I’ve seen plenty of similar questions, but somehow the usual answers don’t work in my case.
I have a simple list of items being loaded from an API, see screenshot. When I press the ‘Set cover’ button, I expect the items to be refreshed and the ‘Set cover’ to appear on the other item. The API cleans all cover
attributes and sets it on the item that is selected.
This is the code for the list:
import React, {useEffect, useState} from "react";
import ImageItem from "./ImageItem";
export default function ImageForm({reviewId}) {
const [items, setItems] = useState([]);
function setCover(id) {
const newList = items.map((item) => {
return {
...item,
cover: (item.id === id),
};
});
console.log('newlist', newList);
setItems(newList);
}
const loadList = () => {
fetch("/api/review/" + reviewId + "/images")
.then((res) => res.json())
.then((json) => {
console.log('useEffect', json);
setItems(json);
});
}
useEffect(() => {
loadList();
}, []);
return (
<>
<div className="row">
<div className="col col-sm-6">
{items.map((data) => {
return <ImageItem key={data.id}
data={data}
reload={loadList}
makeCover2={setCover}
/>;
})}
</div>
</div>
</>
);
}
And this is the code for the item Component:
And this is the code for the item Component:
import React, {useState, useRef, useEffect} from "react";
import axios from "axios";
import {Dropdown} from "react-bootstrap";
export default function ImageItem({data, remove, reload, makeCover2}) {
const [imageData, setImageData] = useState(data);
const makeCover = async function (item) {
axios.post('/api/image/' + imageData.id + '/setcover').then(res => {
if (res.data.errors) {
console.error(res.data.errors);
} else {
makeCover2(imageData.id);
}
}).catch(err => {
console.error(err);
})
}
return (<div className="row my-2 py-2 image-item">
<div className="col p-0 m-0">
<div className="row">
<div className="col">
<img className="img-fluid" alt="" src={'/upload/img/' + imageData.image_name}/>
</div>
<div className="col text-end">
<Dropdown>
<Dropdown.Toggle variant="success" id="dropdown-basic">Actions</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item as="button" onClick={() => setShowForm(true)}>Change comment</Dropdown.Item>
{!imageData.cover && <Dropdown.Item as="button" onClick={makeCover}>Set cover</Dropdown.Item>}
<Dropdown.Item as="button" onClick={() => remove(imageData.id)}>Delete</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
</div>
</div>
</div>)
}
I tried 2 approaches:
- Call a function
makeCover2
that updates all items in the list after the API is done updating the rows and.. - Call a reload function that just calls the
loadList
function, getting all items from the API again.
In both cases, the Set cover
dropdown option stays on the same item in the list. But it should jump to the other item after clicking it. What is going on?
2
Answers
You’ve copied the
data
prop into a state variable. The value indata
is only checked on the very first render to set the initial value ofimageData
. Changes todata
afterwards will have no effect onimageData
.I see no reason for the state to exist, so i recommend you fix this by deleting the state, and using the prop directly:
It seems that the issue is with the state update in the
ImageItem
component. You can try using auseEffect
hook that updates the state when thedata
prop changes:By doing this, the
imageData
will always reflect the latest data prop passed from the parent component.I hope this helps!