Search code examples
javascriptreactjsuse-effect

How to catch the correct return of useeffect in a component from an array of objects?


I have state based components (array of objects). It is necessary to catch unmounting of these components when removing any object from the state. Also, the state cannot be changed in the component. I am currently getting the wrong result on return useEffect when I remove any item from the state.

https://codesandbox.io/s/peaceful-lumiere-ojt2j?file=/src/App.js

import React, { useEffect, useState } from "react";

export default function App() {
  const [list, setList] = useState([]);

  const addHandler = () => {
    setList((prev) => {
      return [...prev, { id: Date.now() }];
    });
  };

  const removeHandler = (id) => {
    setList((prev) => prev.filter((e) => e.id !== id));
  };

  return (
    <div>
      <button onClick={addHandler}>add</button>

      <List list={list} removeHandler={removeHandler} />
    </div>
  );
}

function List({ list, removeHandler }) {

  return (
    <div>
      {list.map((l, i) => (
        <div key={i}>
          {l.id}
          <button onClick={() => removeHandler(l.id)}>remove</button>
        </div>
      ))}

      {list.map((l, i) => (
        <ListItem key={i} id={l.id} />
      ))}
    </div>
  );
}

function ListItem({ id }) {
  useEffect(() => {
    return () => {
      // how to catch unmount of the correct component

      console.log(id); // last added component id
    };
  }, []);

  return null;
}


Solution

  • Try adding the id on the "depedency array" on the ListItem component. This is necessary because then React knows to what state variables that particular effect should fire. If you use an empty array, it will fire when the component mounts, or a better description would be that it fires for no state changes, therefore only once when it mounts and it does't depend on any state.

    If you remove the id on the snippet bellow, the example still works, but React gives a warning telling that the id is missing.

    Also, pass the l.id into the key when calling ListItem. Using the index breaks when you remove anything that it's not the last item.

    import React, { useEffect, useState } from "react";
    
    export default function App() {
      const [list, setList] = useState([]);
    
      const addHandler = () => {
        setList((prev) => {
          return [...prev, { id: Date.now() }];
        });
      };
    
      const removeHandler = (id) => {
        setList((prev) => prev.filter((e) => e.id !== id));
      };
    
      return (
        <div>
          <button onClick={addHandler}>add</button>
    
          <List list={list} removeHandler={removeHandler} />
        </div>
      );
    }
    
    function List({ list, removeHandler }) {
    
      return (
        <div>
          {list.map((l, i) => (
            <div key={i}>
              {l.id}
              <button onClick={() => removeHandler(l.id)}>remove</button>
            </div>
          ))}
    
          {list.map((l, i) => (
            <ListItem key={l.id} id={l.id} />
          ))}
        </div>
      );
    }
    
    function ListItem({ id }) {
      useEffect(() => {
        return () => {
          // how to catch unmount of the correct component
    
          console.log(id); // last added component id
        };
      }, [id]);
    
      return null;
    }