Search code examples
reactjsreduxreact-hooksdispatch

Why React detect the function reference as new?


I have theorical question about custom hooks and use effect when redux is involved. Let`s assume I have this code:

//MyComponent.ts
import * as React from 'react';
import { connect } from 'react-redux';

const MyComponentBase = ({fetchData, data}) => {
   React.useEffect(() => {
      fetchData();
   }, [fetchData]);

   return <div>{data?.name}</data>
}
const mapStateToProps= state => {
   return {
       data: dataSelectors.data(state)
   }
}

const mapDispatchToProps= {
    fetchData: dataActions.fetchData
}

export const MyComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponentBase);

This works as expected, when the component renders it does an async request to the server to fetch the data (using redux-thunk). It initializes the state in the reduces, and rerender the component.

However we are in the middle of a migration to move this code to hooks. Se we refactor this code a little bit:

//MyHook.ts
import { useDispatch, useSelector } from 'react-redux';
import {fetchDataAction} from './actions.ts';

const dataState = (state) => state.data;
export const useDataSelectors = () => {
   return useSelector(dataState);
}

export const useDataActions = () => {
   const dispatch = useDispatch();

   return {
      fetchData: () => dispatch(fetchDataAction)
   };
};

//MyComponent.ts
export const MyComponent = () => {
   const data = useDataSelectors()>
   const {fetchData} = useDataActions();

   React.useEffect(() => {
      fetchData()
   }, [fetchData]);

  return <div>{data?.name}</data>
}

With this change the component enters in an infite loop. When it renders for the first time, it fetches data. When the data arrives, it updates the store and rerender the component. However in this rerender, the useEffect says that the reference for fetchData has changed, and does the fetch again, causing an infinite loop.

But I don't understand why the reference it's different, that hooks are defined outside the scope of the component, they are not removed from the dom or whateverm so their references should keep the same on each render cycle. Any ideas?


Solution

  • useDataActions is a hook, but it is returning a new object instance all the time

    return {
      fetchData: () => dispatch(fetchDataAction)
    };
    

    Even though fetchData is most likely the same object, you are wrapping it in a new object.

    You could useState or useMemo to handle this.

    export const useDataActions = () => {
       const dispatch = useDispatch();
       const [dataActions, setDataActions] = useState({})
    
       useEffect(() => {
         setDataActions({
          fetchData: () => dispatch(fetchDataAction)
         })
       }, [dispatch]);
    
       return dataActions;
    };