Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a prop function inside of UseEffect?

I want to call a prop function inside of UseEffect. The following code works:

useEffect(() => {
    props.handleClick(id);
  }, [id]);

But lint is complaining about props not being a dependency.

If I do this, then the code no longer works and I have maximum re-render error:

  useEffect(() => {
        props.handleClick(id);
      }, [id, props]);

How can I fix the lint issue?

Sample code:

Parent Component

const ParentGrid = ({rows, columns}) => {

  const [selection, setSelection] = useState(null);

  const handleClick = selectedRows => {
    setSelection(selectedRows.map(i => rows[i]));
  };

  return (
      <ChildGrid
        columns={columns}
        data={data}
        handleClick={handleClick}
      />

Child Component

const ChildGrid = props => {
  const {data, handleClick, ...rest} = props;

  useEffect(() => {
    handleClick(selectedRows);
  }, [selectedRows]);
like image 715
SAKURA Avatar asked Jun 19 '20 06:06

SAKURA


People also ask

Can I use props in useEffect?

Just like the state, we can also use props as a dependency to run the side effect. In this case, the side effect will run every time there is a change to the props passed as a dependency. useEffect(() => { // Side Effect }, [props]);

How do you call a prop function in useEffect?

I want to call a prop function inside of UseEffect. The following code works: useEffect(() => { props. handleClick(id); }, [id]);

Can we use function in useEffect?

The function passed to useEffect is a callback function. This will be called after the component renders. In this function, we can perform our side effects or multiple side effects if we want.

How do I run a function in useEffect?

If we just want to run the useEffect function after the initial render, as a second argument, we can give it an empty array. If we pass a second argument (array), React will run the callback after the first render and every time one of the elements in the array is changed.


2 Answers

I see alot of weird and incorrect answers here so I felt the need to write this.

The reason you are reaching maximum call depth when you add the function to your dependency array is because it does not have a stable identity. In react, functions are recreated on every render if you do not wrap it in a useCallback hook. This will cause react to see your function as changed on every render, thus calling your useEffect function every time.

One solution is to wrap the function in useCallback where it is defined in the parent component and add it to the dependency array in the child component. Using your example this would be:

const ParentGrid = ({rows, columns}) => {
  const [selection, setSelection] = useState(null);

  const handleClick = useCallback((selectedRows) => {
    setSelection(selectedRows.map(i => rows[i]));
  }, [rows]); // setSelection not needed because react guarantees it's stable

  return (
    <ChildGrid
      columns={columns}
      data={data}
      handleClick={handleClick}
    />
  );
}

const ChildGrid = props => {
  const {data, handleClick, ...rest} = props;

  useEffect(() => {
    handleClick(selectedRows);
  }, [selectedRows, handleClick]);
};

(This assumes the rows props in parent component does not change on every render)

like image 100
Marcus Avatar answered Oct 04 '22 10:10

Marcus


One correct way to do this is to add props.handleClick as a dependency and memoize handleClick on the parent (useCallback) so that the function reference does not change unnecessarily between re-renders.

It is generally NOT advisable to switch off the lint rule as it can help with subtle bugs (current and future)

in your case, if you exclude handleClick from deps array, and on the parent the function was dependent on parent prop or state, your useEffect will not fire when that prop or state on the parent changes, although it should, because the handleClick function has now changed.

like image 30
gaurav5430 Avatar answered Oct 04 '22 09:10

gaurav5430