Search code examples
reactjsreduxreact-reduxreact-router-domredux-thunk

Redux: Why does my useEffect() keeps rerendering its value on every page rerender


I am learning react-redux. I got the following problem:

  1. I make two async api calls (with redux-thunk):
  • the first one to fetch country names (in one object, ex: {countries: [{...}, ...]}.
  1. Those country names I use afterwards to make a second api call, to get all the soccer leagues, that are in those countrys (sometimes, there are none, so I get a null). In this case, the call is made with each countryName separatly. I make out of the results an array.
  2. This arrays length is 255m out of which I filter out the null values and map the leagues names.
  3. After I click on a League's name, a page is rendered ({Link} from "react-router-dom";). NOW my problem occurs
  4. When I click, to get back to my home page (<Link to={"/"} >), both useEffect() are making an api call again. Why?

Here is the code for my useEffect():

const dispatch = useDispatch();
const selectAllCountries = useSelector(state => state.allCountries);
const selectAllLeagues = useSelector(state => state.allLeagues);

useEffect(() => {
    dispatch(allCountries());
}, [dispatch]);

useEffect(() => {
    if(!_.isEmpty(selectAllCountries.data)) {
        selectAllCountries.data.countries.map(el => dispatch(allLeagues(el.name_en)));
    }
}, [dispatch, selectAllCountries.data]);

I tried to make a custom hook and put the useEffect() in there:

const useCountries = getCountries => {useEffect(() => {
dispatch(getCountries());
},[getCountries])}

useCountries(allCountries);

as suggested here: React hooks: dispatch action from useEffect

But it didnt help.

Will be greatful for any help.


ANSWER:

in "./actions/.../allLeagues.js

...
import _ from "lodash";

export const allLeagues = (country) => async (dispatch, getState) => {

    if (!_.isEmpty(getState().allLeagues) && !_.isEmpty(getState().allLeagues.data)) {
        return Promise.resolve();
    } else {
        try {

          ...
        
        }
    }    
}

Question, that was also helpfull: Fetching data from store if exists or call API otherwise in React (take look at answer about getStore())


Solution

  • As mentioned in a comment above, the homepage unmounts when you click to go to a new page. When you go back, the page re-mounts and the effect runs again, triggering another API call. You can prevent the API call by checking whether or not the values already exist in your store. I personally like to do this in the action creator, but you could do it in the effect as well.

    Checking state in the action creator:

    function allLeagues(countryName) {
      return (dispatch, getState) => {
        // Call `getState` and check whether `allLeagues` has been populated yet.
        const { allLeagues } = getState();
    
        if (allLeagues && allLeagues.data && allLeagues.data.length) {
          // You already have the data, no need to make the API call.
          return Promise.resolve();
        }
    
        // No data, make the API call...
      };
    }
    

    Checking state in the effect:

    useEffect(() => {
      // Check whether the league data is set or not.
      if(!_.isEmpty(selectAllCountries.data) && _.isEmpty(selectAllLeagues.data)) {
        selectAllCountries.data.countries.map(el => dispatch(allLeagues(el.name_en)));
      }
    }, [dispatch, selectAllCountries.data, selectAllLeagues.data]);