I am working on a react app using redux and sagas connected to an API.
There's a form component that has two dropdown fields: a Program and a Contact field. The way the form is designed to work is, when the user selects a program, the form uses the programId to fetch all the contacts that have registered for that program. These contacts are then populated as the options for the contact dropdown field. This works and I've implemented it using the componentWillReceiveProps, like this:-
componentWillReceiveProps(nextProps) {
if (nextProps.programId !== this.props.programId) {
this.props.fetchProgramContacts(nextProps.programId);
}
}
Now I'm trying to have an additional feature that autopopulates the form with the programId when this form is accessed from the program's profile page. In this case, since the programId is preloaded into the formData even before the component mounts, the componentWillReceiveProps is not triggered as there is no change in the prop. So I decided to have the programContacts fetching in the componentDidMount lifecycle method, like this:-
componentDidMount() {
if (this.props.programId !== '' && !this.props.programContactData.length) {
this.props.fetchProgramContacts(this.props.programId);
}
}
The logic is that the fetch request must be made only when the programId is not empty and the programContacts are empty. But this goes on an endless loop of fetching.
I discovered that the if statement gets executed over and over because the expressions in the body of the if statement is executed again by the componentDidMount even before the previous fetch request gets returned with the results. And because one of the conditions is to check whether the length of the results array is nonempty, the if statement returns true and so the loop goes on without letting the previous requests reach completion.
What I don't understand is why the if statement must be executed repeatedly. Shouldn't it exit the lifecycle method once the if statement is executed once?
I know that maybe it is possible to use some kind of a timeout method to get this to work, but that is not a robust enough technique for me to rely upon.
Is there a best practice to accomplish this?
Also, is there any recommendation to not use if conditionals within the componentDidMount method?
The actual problem is in the componentWillReceiveProps method itself, the infinite loop is created here. You are checking if current and next programId will not match, and then trigger an action that will make current and next programId not match again. With given action fetchProgramContacts you are somehow mutating the programId. Check your reducers.
One of the solution to this is to have reqFinished (true/false) in your reducer, and then you should do something like this:
In the React lifecycle,
componentDidMount()
is only triggered once.Make sure the call is made from the
componentDidMount
and notcomponentWillReceiveProps
.If the call trully comes from
componentDidMount
, it means you component is recreated every time. It can be checked by adding aconsole.log
in theconstructor
of your component.In any case, you should prefer using the
isFetching
anddidInvalidate
of redux to handle data fetching / refetching.You can see one of my detailed answer of how it works in another question: React-Redux state in the component differs from the state in the store
If I focus on your usecase, you can see below an application of the
isFetching
anddidInvalidate
concept.1. Components
Take a look at the actions and reducers but the trick with redux is to play with the
isFetching
anddidInvalidate
props.The only two questions when you want to fetch your data will be:
You can see below that whenever you select a program you will invalidate the fetched data in order to fetch again with the new programId as filter.
Note: You should use
connect
ofredux
to pass the actions and reducers to your components of course !MainView.js
ProgramDropdown.js
ContactDropdown.js
2. Contact Actions
I'm going to focus only on the contact actions as the program one is nearly the same.
3. Contact Reducers
Hope it helps.