2

I'm using redux-saga to fetch data from API and for that my code looks something like this.

useEffect(()=>{

  getListFromAPI(); // dispatches an action that fetches data

  getDataByResponseFromList(list.userName); // I also need to call another API depending on response from first API.

}, []);

getDataByResponseFromList(list.userName) fetches data from API depending on response from the getListFromAPI().

This resulted in an error "list.userName not defined", which was obvious because list is not defined yet.

to fix this I wrote another useEffect like below.

useEffect(()=>{

  if(!Empty(list))

  getDataByResponseFromList(list.userName); 

}, [list]);

This worked fine, but in other situation I also need to call this code when one more state changes which is a general "connection" state. So my code becomes something like this.

useEffect(()=>{
  if(!Empty(list) && connection)

  getDataByResponseFromList(list.userName); 

}, [list, connection]);

But now on page load this code runs two times, once when list is populated and once connection is setup, I know exactly the problem is occurring but I'm not sure about the right way to fix this. what is right way to fix such issues.

A Solution I tried :

As a solution I created a global variable to keep track for only one time execution.

let firstTimeExecution = true; // for only onetime execution
export const MyComponent = ({propsList}) => {
...

   useEffect(()=>{
     if(!Empty(list) && connection && firstTimeExecution){

       getDataByResponseFromList(list.userName); 
       firstTimeExecution = false;
      }
}, [list, connection]);
  }

This worked perfectly but I'm not sure if this is the best practice and if I should do that.

  • 1
    [This](https://stackoverflow.com/questions/60618844/react-hooks-useeffect-is-called-twice-even-if-an-empty-array-is-used-as-an-ar) might help you. – c0m1t Aug 17 '22 at 10:19
  • Your "if Empty" solution looks good to me – Poyoman Aug 17 '22 at 10:26
  • You can use async await with your getListFromAPI(), so you return the data once fetched and then use it in the getDataByResponseFromList() function – SlothOverlord Aug 17 '22 at 10:44
  • @Epsilon Yes! that's definitely a good solution, but I need to call getListFromAPI conditionally later as per user response, if it was a onetime call, I would have called it in saga itself. – Arpit Mishra Aug 17 '22 at 10:58
  • If you are using React 18 with Strict Mode, [this](https://stackoverflow.com/a/72751196/15288641) might help. – Youssouf Oumar Aug 17 '22 at 11:12

1 Answers1

1

Since you are using sagas, it might be easier to do the orchestration there rather than in the component.

function * someSaga() {
  // wait for connections & list in any order
  const [_, result] = yield all([
    take(CONNECTION), // action dispatched once you have connection
    take(FETCH_LIST_SUCCESS)
  ])
  // call a saga with a userName from the fetch list success action
  yield call(getDataByResponseFromList, result.userName);
}

If you expect FETCH_LIST_SUCCESS to happen multiple times and want to call the getData saga every time:

function * someSaga() {
  // Do nothing until we have connection 
  yield take(CONNECTION)
  // call a saga with a userName from the fetch list success action
  yield takeEvery(FETCH_LIST_SUCCESS, function*(action) {
    yield call(getDataByResponseFromList, action.userName);
  })
}

If you need to get the data for every getListFromAPI but it can be called multiple times before you get the connection (I am assuming here you don't the the connection for the getListFromAPI itself), you can also buffer the actions and then process them once you have it.

function * someSaga() {
  const chan = yield actionChannel(FETCH_LIST_SUCCESS)
  yield take(CONNECTION)
  yield takeEvery(chan, function*(action) {
    yield call(getDataByResponseFromList, action.userName);
  })
}
Martin Kadlec
  • 4,702
  • 2
  • 20
  • 33