Block sending requests twice react-redux (hooks)

Viewed 1145

I have some hook for fetching countries array from api :

import { useSelector, useDispatch } from 'react-redux'

const useCountries = () => {
 const dispatch = useDispatch()

 const { countries } = useSelector(data => data)

 useEffect(() => {
   if (!countries.length) {
     dispatch(getCountries())
   }
 }, [countries])

  return {
    countries
  }
}

I have some components which aren't on the same level of DOM , and aren't related to each other

const FirstComponent = () => {
  const {countries} = useCountries()

  // some using of countries array
}

const SecondComponent = () => {
  const {countries} = useCountries()

  // some using of countries array
}

When page loading 2 of these components, are executed, and 2 actions are dispatched. Because countries aren't selected yet in first and in the second component it calls API request. My question is, how can I prevent sending the second request if some request was sent. Make some rule for sending request only once, if try to send the second one, then block it. Or what is the correct solution or approach in my case?

Also, I've tried a case like this :

  const {
    external: { response, isInitial }
  } = useSelector(data => data)

  useEffect(() => {
    if (!response && isInitial) {
      dispatch(setIsInitial(false))

      fetch()
        .then(res => res.json())
        .then(data => {
           setResponse(data)
        })
    }
  }, [dispatch, response, isInitial])

Here is my reducer :

const initialState = {
  response: null,
  isInitial: true
}

export default function external(state = initialState, action) {
  switch (action.type) {
    case types.SET_RESPONSE:
      return {
        ...state,
        response: action.payload.data
      }
    case types.SET_INITIAL:
      return {
        ...state,
        isInitial: action.payload.data
      }

    default:
      return state
  }
}

But it sends 2 requests

2 Answers

You can modify your useCountries as below:

const useCountries = () => {
 const dispatch = useDispatch()

 const countries  = useSelector(state => state.countries);
 const isFetching = useSelector(state => state.isFetching);

 useEffect(() => {
   if (!countries.length && !isFetching) {
     dispatch(getCountries()) 
   }
 }, [countries, isFetching])

  return {
    countries
  }
}

You would need to flip the isFetching flag in your getCountries function and flip it again when the api has resolved.

------------------EDIT after additional information-----------------------

You would need to chain your dispatch calls. This is to ensure that flag is set before you make the api call.

To achieve that you can make dispatch thenable in the following way.

dispatch returns either of two:

For sync action (like dispatch ({type: 'ACTION'}) it will return action object ({type: 'ACTION'} in my example)

For thunk actions (action creators which return functions) it returns the same result returned from action creator.

This is the first case.

const syncAction = (arg1, arg2) => {
    return (dispatch, getState) => {
        return Promise.resolve(arg1 + arg2);
    }
}

This would be the second case.

const asyncAction = (arg1, arg2) => {
  return (dispatch, getState) => {
    return fetch(/* some request */)
      .then(response => dispatch({ type: "RESPONSE_RECEIVED", payload: response }));
  };
};

Now armed with two above, you can do the following:

dispatch(syncAction(...args)).then(() => {
    dispatch(asyncAction())
});

In your case syncAction - isInitial and asyncAction - getCountries().

Addind dispatch to useEffect dependencies array whould solve the issue.

 const dispatch = useDispatch()

 const { countries } = useSelector(data => data)

useEffect(() => {
   if (!countries.length) {
     dispatch(getCountries())
   }
 }, [countries, dispatch])
Related