Why does the rendering twice in react.js - reactjs

Why does the rendering occur twice in this code?
I tried to remove the <React.StrictMode></React.StrictMode>
but I can't fixed the re-rendering
I don't think it's a "useState" issue
const [memberArray, setMemberArray] = useState<any[]>([]);
const [memberArea, setMemberArea] = useState(0);
console.log("memberArea: " + memberArea);
const getData = async () => {
try {
const res: any = await axios.get(
`api/v1/member?memberCode=${memberNum}`
);
const data: any = res.data.data.memberList;
setMemberArray([...data]);
} catch (err: any) {
console.log(err);
}
};
useEffect(() => {
getData();
}, [memberArea]);

Your effect has one dependecy, memeberArea, meaning it will execute effect each time memberArea changes. Problem with you approach is because your effect depends on something which is updated by effect itself, without any safe condition to avoid infinity loop or unnecessary state updates.
First time element renders when mounted. Then you step into useEffect and update state inside effect, thus forcing additional, second, rerender, because memberArea changed by effect. Problem with your code are not rerenders, but the fact that you will end up with another effect execution because memberArea changed in first call and this will lead you to one extra call of getData, so you end up with 2 rerenders(which is fine) and 2 subsequent api calls(which is wrong because you will update state with same value twice). Special case is your memberArray that, with the current solution, will be updated twice, and since it is array(complex type) you will override reference twice thus forcing jsx part that consumes memberArray to rerender 3 times(initially, on first and on second api call).
If you want to call getData only once and prevent this side effect you should remove dependency and leave empty array, thus forcing effect to be executed only once, otherwise you would need to insert some checks inside effect to prevent unnecessary state updates like you do now.

Related

ReactJS - Inefficient useEffect runs four times

I have a useEffect function that must wait for four values to have their states changed via an API call in a separate useEffect. In essence the tasks must happen synchronously. The values must be pulled from the API and those stateful variables must be set and current before the second useEffect can be called. I am able to get the values to set appropriately and my component to render properly without doing these tasks synchronously, I have a ref which changes from true to false after first render (initRender), however I find the code to be hacky and inefficient due to the fact that the second useEffect still runs four times. Is there a better way to handle this?
//Hook for gathering group data on initial page load
useEffect(() => {
console.log("UseEffect 1 runs once on first render");
(async () => {
const response = await axios.get(`${server}${gPath}/data`);
const parsed = JSON.parse(response.data);
setGroup(parsed.group);
setSites(parsed.sites);
setUsers(parsed.users);
setSiteIDs(parsed.sitesID);
setUserIDs(parsed.usersID);
})();
return function cleanup() {};
}, [gPath]);
//Hook for setting sitesIN and usersIN values after all previous values are set
useEffect(() => {
console.log("This runs 4 times");
if (
!initRender &&
sites?.length &&
users?.length &&
userIDs !== undefined &&
siteIDs !== undefined
) {
console.log("This runs 1 time");
setSitesIN(getSitesInitialState());
setUsersIN(getUsersInitialState());
setLoading(false);
}
}, [sites, siteIDs, users, userIDs]);
EDIT: The code within the second useEffect's if statement now only runs once BUT the effect still runs 4 times, which still means 4 renders. I've updated the code above to reflect the changes I've made.
LAST EDIT: To anyone that sees this in the future and is having a hard time wrapping your head around updates to stateful variables and when those updates occur, there are multiple approaches to dealing with this, if you know the initial state of your variables like I do, you can set your dependency array in a second useEffect and get away with an if statement to check a change, or multiple changes. Alternatively, if you don't know the initial state, but you do know that the state of the dependencies needs to have changed before you can work with the data, you can create refs and track the state that way. Just follow the examples in the posts linked in the comments.
I LIED: This is the last edit! Someone asked, why can't you combine your different stateful variables (sites and sitesIN for instance) into a single stateful variable so that way they get updated at the same time? So I did, because in my use case that was acceptable. So now I don't need the 2nd useEffect. Efficient code is now efficient!
Your sites !== [] ... does not work as you intend. You need to do
sites?.length && users?.length
to check that the arrays are not empty. This will help to prevent the multiple runs.

React - update multiple useEffect / useCallback dependencies but only call effect once

I have a paginated list of data that I am updating based on a filter and an offset (page)
When the offset is updated (next/prev page) I hit the API and get new data. When the filter is updated I reset the offset to 0.
The problem is, when the filter is updated and the offset is updated it causes the useEffect to be fired twice. Which in turn calls the api twice.
const [filter, setFilter] = useState('All');
const [offset, setOffset] = useState(0);
onFilterChange = (value) => {
setFilter(value);
offset !== 0 && setOffset(0);
}
getDataFromAPI = useCallback(() => {
const endpoint = `https://example.com/data?filter=${filter}&offset=${offset}`;
CallApi(endpoint);
}, [offset, filter])
useEffect(getDataFromAPI, [getDataFromAPI]);
I think the fix would be to get rid of useEffect in such case. Sometimes it is used needlessly. Just call CallApi inside the onFilterChange and onOffsetChange handlers with the new values that were set, and that's it.
Here are some relevant quotes from Dan Abramov:
In hindsight, my personal conviction has become that if some effect
can’t safely over-fire (e.g. fire twice instead of once), there is a
problem. Usually things that can’t over-fire are related to user
actions ("buy", "send", "finish"). Actions start in event handlers.
Use them!
To sum up, if something happens because a user did something,
useEffect might not be the best tool.
On the other hand, if an effect merely synchronizes something (Google
Map coordinates on a widget) to the current state, useEffect is a good
tool. And it can safely over-fire.
PS But just to note I thought in your case react would batch the two different set state calls inside the filter change handler (hence call render once), but it seems it doesn't? In any case you may still prefer the recommendation in the beginning of my answer to remove useEffect.

React "interval" has always the same state

I am using react 16.10 with typescript.
I have this code:
const [state, setState] = useState<State>({
test: 1
});
//On component mount we start our interval
useEffect(() => {
const timerID = setInterval(timer, 5000); //every 5 seconds
return function cleanup() {
//When we leave component we stop the timer
clearInterval(timerID);
};
}, []);
function timer() {
if(state.test === 3){
//HE WILL NEVER ENTER THIS CODE FUNCTION
}
setState({...state, test: 3}); // Next time we should have the value 3, BUT IT HAS NEVER THIS VALUE?!?!
}
return (
<>
<span>The output is {state.test}</span> //First it is showing 1, after 5 seconds 3. Working great
</>
);
I am changing the value of test to the number 3 in the interval "timer". setState is working fine. I can see the value in my component, seeing the number switching from 1 to 3.
But in the timer function the value is never changed. It has every time the default value of 1.
What I am doing wrong?
You need to add dependency to useEffect
//On component mount we start our interval
useEffect(() => {
const timerID = setInterval(timer, 5000); //every 5 seconds
return function cleanup() {
//When we leave component we stop the timer
clearInterval(timerID);
};
}, [state.test]); // <- here add dependency
Reason
Your effect function is called only once when component is mounted and it stored timer functions reference. now when you state changes your timer function is also updated outside but not inside of useEffect.
useEffect still uses old reference when state was 1 so inside it State always going to be 1 for that referred timer function
Now when you pass state.test as dependency. when state get changed your effect will updated and it now start using new timer function which has new state.
So now, you can have updated state in your timer function. and your condition can evaluate correctly.
if any doubts please comment.
You are not doing anything wrong, your useEffect() has a completely different value in memory and without knowing this behavior about useEffect() you have nothing in there telling useEffect() to stop looking at that old value and start looking at the new value. As Hardik wrote, your useEffect() is called only once, so you still have that old value that was originally called in there and useEffect has no idea that your timer has changed since. It will be referencing that original value forever.
What you can do is completely remove the empty array as the second argument and you will notice the difference in behavior.
Using a direct reference to the variable you are using in your state as suggested by Hardik seems to be the way to go.
So again, useEffect() is not being called a second time and as a result, nothing inside it is being ran again so it all in stale reference.
One of the tips the facebook team gives to mitigate this bug:
When you have a useEffect function that references a state, props, or context values, add them to your dependency list. In other words, if you have a props called trackId, you would want to do something like this:
useEffect(() => {
trackId
}, [trackId]);
I see a couple of potential issues, first of all you need to be calling this.state and this.setState. I'd guess state is undefined but this.state won't be. You also don't need to spread your state in your setState function, this.setState({ test: 3}); is good enough, the setState function does this for you.
Secondly you need to update state for every change, it looks like you're only updating if the test value is 3, I'm surprised it's ever 3 with this implementation

When exactly do React Hook states get set?

I am trying to understand the order of execution for the following piece of code involving React Hook states:
const App = () => {
const [ searchedCountry, setSearchedCountry ] = useState('');
const [ filteredArr, setFilteredArr ] = useState([]);
const [ filteredLength, setFilteredLength ] = useState(0);
//...
const filterCountries = (event) => {
event.preventDefault();
setFilteredArr(countries.filter(country => country.name.includes(searchedCountry)));
setFilteredLength(filteredArr.length);
console.log("filtered arr length", filtered.length);
}
//...
}
When filterCountries is triggered, setFilteredArr sets the state, filteredArr, to an array filtered by a query. However, when exactly does filteredArr get set?
filteredArr.length returns 0, meaning filteredArr has not been set yet, even after calling setFilteredArr.
At first, I thought by executing setFilteredArr, the component re-renders, causing execution to skip the method calls after setFilteredArr. That would explain why filteredArr.length is 0. However, the console.log is still called, meaning after a component re-renders, the order of execution is actually resumed.
What is going on? When exactly does filteredArr get set?
The thing to remember is: all your state variables are local variables. They only exist for this particular time the component rendered. So on the first render, console.log("filtered arr length", filteredArr.length); is referring to the array that exists on that first render. filteredArr will never be assigned a new array (it can't, it's a const), and unless you mutate the array (which you shouldn't), the length of that array will always be 0.
When you call setFilteredArr, this instructs react to rerender the component. React might do the rendering synchronously, or it might wait to try to batch up changes. When that 2nd render happens, you make a call to useState and get back the new value. This is assigned to a local variable named filteredArr, but this is a completely different variable than the one we had on the first render. The console.log statement in that first render, will have no way to access the variable in the second render. But the second render has access to it, so any logging you do the second time will show the second array.
setState or 'setFilteredLength' is an async operation. After you call it, it will take some time to update the state (as it is async, it will not wait for that update. It will simply execute the next line) so when it executes the console.log the value has not changed -> yet

useState React hook always returning initial value

locationHistory is always an empty array in the following code:
export function LocationHistoryProvider({ history, children }) {
const [locationHistory, setLocationHistory] = useState([])
useEffect(() => history.listen((location, action) => {
console.log('old state:', locationHistory)
const newLocationHistory = locationHistory ? [...locationHistory, location.pathname] : [location.pathname]
setLocationHistory(newLocationHistory)
}), [history])
return <LocationHistoryContext.Provider value={locationHistory}>{children}</LocationHistoryContext.Provider>
}
console.log always logs []. I have tried doing exactly the same thing in a regular react class and it works fine, which leads me to think I am using hooks wrong.
Any advice would be much appreciated.
UPDATE: Removing the second argument to useEffect ([history]) fixes it. But why? The intention is that this effect will not need to be rerun on every rerender. Becuase it shouldn't need to be. I thought that was the way effects worked.
Adding an empty array also breaks it. It seems [locationHistory] must be added as the 2nd argument to useEffect which stops it from breaking (or no 2nd argument at all). But I am confused why this stops it from breaking? history.listen should run any time the location changes. Why does useEffect need to run again every time locationHistory changes, in order to avoid the aforementioned problem?
P.S. Play around with it here: https://codesandbox.io/s/react-router-ur4d3?fontsize=14 (thanks to lissitz for doing most the leg work there)
You're setting up a listener for the history object, right?
Assuming your history object will remain the same (the very same object reference) across multiple render, this is want you should do:
Set up the listener, after 1st render (i.e: after mounting)
Remove the listener, after unmount
For this you could do it like this:
useEffect(()=>{
history.listen(()=>{//DO WHATEVER});
return () => history.unsubscribe(); // PSEUDO CODE. YOU CAN RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[]); // THIS EMPTY ARRAY MAKES SURE YOUR EFFECT WILL ONLY RUN AFTER 1ST RENDER
But if your history object will change on every render, you'll need to:
cancel the last listener (from the previous render) and
set up a new listener every time your history object changes.
useEffect(()=>{
history.listen(()=>{//DO SOMETHING});
return () => history.unsubscribe(); // PSEUDO CODE. IN THIS CASE, YOU SHOULD RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[history]); // THIS ARRAY MAKES SURE YOUR EFFECT WILL RUN AFTER EVERY RENDER WITH A DIFFERENT `history` OBJECT
NOTE: setState functions are guaranteed to be the same instance across every render. So they don't need to be in the dependency array.
But if you want to access the current state inside of your useEffect. You shouldn't use it directly like you did with the locationHistory (you can, but if you do, you'll need to add it to the dependency array and your effect will run every time it changes). To avoid accessing it directly and adding it to the dependency array, you can do it like this, by using the functional form of the setState method.
setLocationHistory((prevState) => {
if (prevState.length > 0) {
// DO WHATEVER
}
return SOMETHING; // I.E.: SOMETHING WILL BE YOUR NEW STATE
});

Resources