Search code examples
reactjsreact-hooksredux-thunk

React Hook useEffect has a missing dependency for redux action as parameters


I found many similar questions here about React Hook useEffect has a missing dependency. I have already checked them, but I didn't find solutions as I faced. I want to pass redux thunk function as a parameter to React custom hook.

Below is my code and it is working fine. But, I got dependency missing warning, I don't want to add ignore warning eslint. If I add dispatchAction to dependency array list, it is dispatching again and again because redux thunk asyn function has fulfilled, reject, pending.

Custom Hook

const useFetchData = (dispatchAction, page) => {
  const dispatch = useDispatch();
  const [loadMoreLoading, setLoadMoreLoading] = useState(false);
  const [errorMsg, setErrorMsg] = useState();

  useEffect(() => {
    const fetchData = async () => {
      setLoadMoreLoading(true);
      const resultAction = await dispatch(dispatchAction);
      if (resultAction.meta.requestStatus === 'rejected') {
        setErrorMsg(resultAction.payload.message);
      }
      setLoadMoreLoading(false);
    };
    fetchData();
  }, [dispatch, page]);

  return [loadMoreLoading, errorMsg]; // it is asking for adding dispatchAction.

My component

const SomeListing = ({userId}) => {
  const [page, setPage] = useState(1);
  const [loadMoreLoading, errorMsg] = useFetchData(
    fetchPropertyByUserId({userId: userId, page: page}),
    page,
  );
}

So, is there any way to be able to add redux thunk function in react custom hook?


Solution

  • The function fetchPropertyByUserId, when called i.e. fetchPropertyByUserId({userId: userId, page: page}), returns an "actionCreator" function.

    Hence, when you call this function at the place of first parameter of your hook useFetchData, it returns a new "actionCreator" function each time (we know that hooks are called at each render):

    In SomeListing.jsx:

    const [loadMoreLoading, errorMsg] = useFetchData(
      fetchPropertyByUserId({userId: userId, page: page}), // <-- Here: it returns a new "actionCreator" function at call (render)
      page,
    );
    

    And, as soon as you put this function (first parameter of the hook i.e. dispatchAction) as a dependency of useEffect, it should cause an infinite execution of the effect because, now we know, that dispatchAction is getting created (hence, changed) at every render.

    In useFetchData.js:

    export const useFetchData = (dispatchAction, page) => {
    
      // ...
    
      useEffect(() => {
        const fetchData = async () => {
          setLoadMoreLoading(true)
          const resultAction = await dispatch(dispatchAction)
          if (resultAction.meta.requestStatus === 'rejected') {
            setErrorMsg(resultAction.payload.message)
          }
          setLoadMoreLoading(false)
        }
        fetchData()
      }, [dispatch, dispatchAction, page]) // <-- "dispatchAction" added here
    
      // ...
    

    How to fix it?

    Pass a memoized actionCreator function:

    In SomeListing.jsx:

    export const SomeListing = ({ userId }) => {
      const [page, setPage] = useState(1)
    
      // Here: "fetchPropertyByUserIdMemo" is memoized now
      const fetchPropertyByUserIdMemo = useMemo(
        () => fetchPropertyByUserId({ userId: userId, page: page }),
        [page, userId]
      )
    
      const [loadMoreLoading, errorMsg] = useFetchData(fetchPropertyByUserIdMemo, page)
    
      // ...
    }