skip to Main Content

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?

enter image description here

2

Answers


  1. const [imageData, setImageData] = useState(data);
    

    You’ve copied the data prop into a state variable. The value in data is only checked on the very first render to set the initial value of imageData. Changes to data afterwards will have no effect on imageData.

    I see no reason for the state to exist, so i recommend you fix this by deleting the state, and using the prop directly:

    export default function ImageItem({ data, remove, reload, makeCover2 }) {
      const makeCover = async function (item) {
        axios
          .post("/api/image/" + data.id + "/setcover")
          .then((res) => {
            if (res.data.errors) {
              console.error(res.data.errors);
            } else {
              makeCover2(data.id);
            }
          })
          .catch((err) => {
            console.error(err);
          });
      };
    
      return (
        // ...
        <img className="img-fluid" alt="" src={'/upload/img/' + data.image_name}/>
        // ...
        {!data.cover && (
          <Dropdown.Item as="button" onClick={makeCover}>
            Set cover
          </Dropdown.Item>
        )}
        <Dropdown.Item as="button" onClick={() => remove(data.id)}>
          Delete
        </Dropdown.Item>
        // ...
      );
    }
    
    
    Login or Signup to reply.
  2. It seems that the issue is with the state update in the ImageItem component. You can try using a useEffect hook that updates the state when the data prop changes:

    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);
    
        useEffect(() => {
            setImageData(data);
        }, [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>)
    
    }
    

    By doing this, the imageData will always reflect the latest data prop passed from the parent component.

    I hope this helps!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search