Building a site where users upload images/video, in the component that handles that, I have the images load into the page after being uploaded, so that the user can make sure they want to post them, and have the option to remove them if they like. I originally had this as a Class-based view, and everything worked as expected, but now, after changing to a functional component, after uploading images, React doesn't seem to notice the changes to imageLinks (even though the console shows that imageLinks is getting added to), until I update something else like the post title, then they all load in as expected. Once loaded in, if I click the delete button, React instantly updates and the photos/videos no longer shows up, as expected.
Any ideas as to why this is behaving so oddly? The fact that the deleteMedia function works just fine is what really weirds me out.
I set up my variables like this
export default function NewPost(props) {
const [postCategories, setPostCategories] = useState([]);
const [postTitle, setPostTitle] = useState();
const [postDescription, setPostDescription] = useState();
const [postHashtags, setPostHashtags] = useState();
const [imageLinks, setImageLinks] = useState([]);
...}
In my component, I have this to handle uploading and deleting files.
const uploadMedia = async (file) => {
var csrftoken = getCookie("csrftoken");
var media_id = makeMediaID();
let formData = new FormData();
formData.append("content_id", contentID);
formData.append("creator_id", USER_ADDRESS);
formData.append("content_media", file);
formData.append("media_id", media_id);
const response = await fetch("/api/newpost/media/", {
method: "POST",
headers: {
"X-CSRFToken": csrftoken,
},
body: formData,
});
console.log(response);
const r_json = await response.json();
if (r_json.success) {
let tempLinks = imageLinks;
tempLinks.push({
img: r_json.image_url,
id: r_json.m_id,
type: r_json.m_type,
});
setImageLinks(tempLinks);
console.log(imageLinks);
} else {
console.log(r_json.message);
}
};
const deleteMedia = async (media) => {
var csrftoken = getCookie("csrftoken");
let formData = new FormData();
formData.append("media_id", media);
formData.append("creator_id", USER_ADDRESS);
const response = await fetch("/api/newpost/media/delete/", {
method: "DELETE",
headers: {
"X-CSRFToken": csrftoken,
},
body: formData,
});
console.log(response);
const r_json = await response.json();
if (r_json.success) {
let tempLinks = imageLinks.filter((item) => item.id !== media);
setImageLinks(tempLinks);
} else {
console.log("Media deletion error");
}
};
And in my render, I have this, which worked just fine when it was a class-based component.
{imageLinks.map((item) => (
<Grid item xs={9} align="center" key={item.img}>
<Card style={{ maxWidth: 550, margin: 15 }}>
<div
style={{
display: "flex",
alignItem: "center",
justifyContent: "center",
}}
>
<CardMedia
style={{
width: "100%",
maxHeight: "550px",
}}
component={item.type}
image={item.img}
controls
title={String(item.img)}
/>
</div>
<div align="center">
<CardActions>
<Button
endIcon={<DeleteForeverIcon />}
label="DELETE"
color="secondary"
variant="contained"
onClick={() => deleteMedia(item.id)}
>
REMOVE
</Button>
</CardActions>
</div>
</Card>
</Grid>
))}
CodePudding user response:
Issue
The issue is a state mutation.
const [imageLinks, setImageLinks] = useState([]);
tempLinks
is a reference to the imageLinks
state. You are pushing directly into this tempLinks
array and saving the same array reference back into state. The code is also incorrectly attempting to log the state immediately after enqueueing the state update. This doesn't work as React state updates are asynchronously processed.
if (r_json.success) {
let tempLinks = imageLinks; // <-- reference to state
tempLinks.push({ // <-- state mutation!
img: r_json.image_url,
id: r_json.m_id,
type: r_json.m_type,
});
setImageLinks(tempLinks); // <-- same reference back into state
console.log(imageLinks); // <-- only logs current state
}
The imageLinks
state array reference never changes so React doesn't see this as state actually being updated.
Solution
Create and return a new array reference for React.
if (r_json.success) {
setImageLinks(imageLinks => [ // <-- new array reference
...imageLinks, // <-- shallow copy previous state
{ // <-- append new element object
img: r_json.image_url,
id: r_json.m_id,
type: r_json.m_type,
}
]);
}
Use a separate useEffect
hook to console log the state updates.
useEffect(() => {
console.log(imageLinks);
}, [imageLinks]);