I have an array of objects where one of the properties of each object is a file name. I then use that file name to get a pre-signed AWS S3 url from an API endpoint I wrote. I then want to map over each object and display info about it, including displaying the image. For whatever reason this isn't working. About half the time it just doesn't display anything related to the image... Not the actual image nor any "no image" icon. The other half of the time, when it does work it just flashes the image for a second before reverting to the little "no image" icon. I have no idea why this isn't working because I have it working in another component that doesn't require mapping, and am even more confused because the problem doesn't manifest consistently...
Here's the code for the component that's not working:
export const MemeList = () => {
const [state, setState] = useState<State>({
memes: []
});
useEffect(() => {
fetch(`${process.env.REACT_APP_BACKEND_URL}/memes/`)
.then((response) => response.json())
.then((memes) => {
var newMemes = memes.map((meme) => {
fetch(`${process.env.REACT_APP_BACKEND_URL}/images/${meme.file}`)
.then((response) => response.json())
.then((image) => {
meme.image = image;
});
return meme;
});
setState({ memes: newMemes });
console.log(newMemes);
});
}, []);
return (
<>
{(state.memes.length == 0) ? (
<p>Loading...</p>
) : (
<div className="container">
<h2 className="comp-title">Featured Memes</h2>
<div className="featured-memes">
{
state.memes.slice(0,3).map((meme,i) => {
return (
<div id={meme.id} className="meme">
<div className="vote-container">
<Vote className="vote" upvotes={meme.upvotes} category='memes' id={meme.id} upvoters={meme.upvoters} downvoters={meme.downvoters} />
</div>
<Link id={meme.id} to={`/memes/${meme.id}`}>
<div className="meme-container">
<div className="meme-head">
<h2 className="meme-title">{meme.title}</h2>
</div>
<div className="meme-body">
<img className="meme-img" src={meme.image} />
</div>
</div>
</Link>
</div>
)
})
}
</div>
</div>
)}
</>
);
Here's the code for the working component:
useEffect(() => {
setLoading(true);
fetch(`${process.env.REACT_APP_BACKEND_URL}/memes/${memeId}`)
.then((response) => response.json())
.then((meme) => {
fetch(`${process.env.REACT_APP_BACKEND_URL}/images/${meme.file}`)
.then((response) => response.json())
.then((image) => {
setState({ ...state, id: meme.id, title: meme.title, upvotes: meme.upvotes, upvoters: meme.upvoters, downvoters: meme.downvoters, comments: meme.comments, file: image, posterAddress: meme.posterAddress, category: meme.category, stakeWithdrawn: meme.stakeWithdrawn });
})
.then(setLoading(false))
.catch((err) => window.alert(err));
})
.catch((err) => window.alert(err));
}, []);
return (
<>
{(state.file == '') ? (
<p>Loading</p>
) : (
<div className="meme-comp-container">
<div className="meme-comp-vote-container">
<Vote className="vote" upvotes={state.upvotes} category="memes" id={state.id} upvoters={state.upvoters} downvoters={state.downvoters} />
</div>
<div className="meme-comp-meme-container">
<div className="meme-comp-meme-head">
<h2 className="meme-comp-title">{state.title}</h2>
</div>
<div className="meme-comp-meme-body">
<img className="meme-comp-meme-img" src={state.file} />
</div>
</div>
</div>
)}
</>
)
**EDIT: ** quick update, so I think I found the problem but have no idea how to fix it. In the useEffect hook if I print "newMemes" it displays everything correctly, but if I map over newMemes and print just the image property it says undefined.
useEffect(() => {
fetch(`${process.env.REACT_APP_BACKEND_URL}/memes/`)
.then((response) => response.json())
.then((memes) => {
var newMemes = memes.map((meme) => {
fetch(`${process.env.REACT_APP_BACKEND_URL}/images/${meme.file}`)
.then((response) => response.json())
.then((image) => {
meme.image = image;
});
return meme;
});
setState({ memes: newMemes });
newMemes.slice(0,3).map((newMeme) => console.log(newMeme.image));
console.log(newMemes);
});
}, []);
gives output: undefined undefined undefined [0: category: "meme" comments: 0 createdAt: "2021-09-11T23:12:58.956Z" downvoters: [] file: "corp_site_logo_1631401978598.png" id: 11 image: "https://--.s3.amazonaws.com/corp_site_logo_1631401978598.png?AWSAccessKeyId=&Expires=&Signature=W%=" posterAddress: "0x8a8b5a97978db4a54367d7dcf6a50980990f2373" stakeWithdrawn: false title: "test" updatedAt: "2021-09-11T23:12:58.956Z" upvoters: ['0x8a8b5a97978db4a54367d7dcf6a50980990f2373'], 1: ..., 2: ..., ...]
CodePudding user response:
If you compare the working and non-working code you should notice the difference immediately. In the working code you are requesting a single meme and fetching its image, singly, and enqueueing a state update. In the non-working code you are requesting multiple memes, fetching the images individually, enqueueing a state update before these requests have likely resolved, and then mutating the state objects. This is why you are seeing inconsistent results with the first, non-working code snippet.
You could individually request each image and use a functional state update to add the augmented meme
object to state.
useEffect(() => {
fetch(`${process.env.REACT_APP_BACKEND_URL}/memes/`)
.then((response) => response.json())
.then((memes) => {
memes.forEach(meme =>
fetch(`${process.env.REACT_APP_BACKEND_URL}/images/${meme.file}`)
.then((response) => {
if (!response.ok) throw new Error('response not ok');
return response.json();
})
.then((image) => {
setState(prevState => ({
...prevState,
memes: [
...prevState.memes,
{
...meme,
image,
}
],
}));
})
.catch(error => {
// handle error
})
.finally(() => {
// clear any loading state
});
);
}, []);