React useState, useEffect not working as expected [closed] - reactjs

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I have a use case that I'd like to solve it with React hooks, but it's not working as I expected.
Expectation: Not on top will be shown on scrolling down and will disappear when scrolling back on top of the document.
Current: Not on top shown on scrolling down but not disappear when scrolling back on top of the document.
Code sandbox link: https://codesandbox.io/s/wkroy1r2xl
Thanks in advance.

The component adds the event listener once: only on mount. At that time, it adds the handleScroll listener that is "captured" with that specific isHeaderMoved value at that specific point in time. Whenever the page is scrolled, the callback that will be called is the one with that initial isHeaderMoved value. The current code basically calls handleScroll with false over and over.
In short, you need to add isHeaderMoved as a dependency to the effect. You should also move the handleScroll listener inside the effect, so its dependencies can be tracked more properly by the hooks eslint plugin.
useEffect(
() => {
const handleScroll = () => {
if (window.pageYOffset > 0) {
if (!isHeaderMoved) {
setIsHeaderMoved(true)
}
} else if (isHeaderMoved) {
setIsHeaderMoved(false)
}
}
window.addEventListener("scroll", handleScroll)
return () => {
window.removeEventListener("scroll", handleScroll)
}
},
[isHeaderMoved],
)
Refer to these sections from Dan Abromov's posts on the subject (which have examples that can explain it better than I could):
https://overreacted.io/a-complete-guide-to-useeffect/#each-render-has-its-own-event-handlers
https://overreacted.io/a-complete-guide-to-useeffect/#each-render-has-its-own-effects
https://overreacted.io/a-complete-guide-to-useeffect/#moving-functions-inside-effects
It's worth going through the whole post to understand things fully, if you have the time.
From skyboxer's comment, it occurred to me that this would also work, but I'll keep all of the above for informational purposes.
useEffect(() => {
const handleScroll = () => {
setIsHeaderMoved(window.pageYOffset > 0)
}
window.addEventListener("scroll", handleScroll)
return () => {
window.removeEventListener("scroll", handleScroll)
}
}, [])

This should work, you have to pass the window.pageYOffset or isHeaderMoved to the useEffect to fire it in every time it changes, if you left it [] it'll fire once.
useEffect(
() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
},
[isHeaderMoved]
);
I think you also can use useRef() to do this.

I had the same annoying problem once
try adding a key prop on the component when it is created in the parent code
<yourcomponent key="some_unique_value" />
This is because in most cases, especially when your component is reused, it may usually re-render only with some changes instead of actually creating it again when you reuse it, based on the way it's created, Hence the useEffect is not going to be called. eg inside Switch, Router, loops, conditionals...
So adding a key will prevent this from happening. If it is in a loop, you need to make sure each element is unique, maybe by including the index i in the key if you can't find any better unique key.

Related

Why this react code requests data from server twice [duplicate]

I have a counter and a console.log() in an useEffect to log every change in my state, but the useEffect is getting called two times on mount. I am using React 18. Here is a CodeSandbox of my project and the code below:
import { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(5);
useEffect(() => {
console.log("rendered", count);
}, [count]);
return (
<div>
<h1> Counter </h1>
<div> {count} </div>
<button onClick={() => setCount(count + 1)}> click to increase </button>
</div>
);
};
export default Counter;
useEffect being called twice on mount is normal since React 18 when you are in development with StrictMode. Here is an overview of what they say in the documentation:
In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React will support remounting trees using the same component state used before unmounting.
This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects do not properly clean up subscriptions in the destroy callback, or implicitly assume they are only mounted or destroyed once.
To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.
This only applies to development mode, production behavior is unchanged.
It seems weird, but in the end, it's so we write better React code, bug-free, aligned with current guidelines, and compatible with future versions, by caching HTTP requests, and using the cleanup function whenever having two calls is an issue. Here is an example:
/* Having a setInterval inside an useEffect: */
import { useEffect, useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((count) => count + 1), 1000);
/*
Make sure I clear the interval when the component is unmounted,
otherwise, I get weird behavior with StrictMode,
helps prevent memory leak issues.
*/
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
};
export default Counter;
In this very detailed article called Synchronizing with Effects, React team explains useEffect as never before and says about an example:
This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs. From the user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, and then pressing Back. React verifies that your components don’t break this principle by remounting them once in development.
For your specific use case, you can leave it as it's without any concern. And you shouldn't try to use those technics with useRef and if statements in useEffect to make it fire once, or remove StrictMode, because as you can read on the documentation:
React intentionally remounts your components in development to help you find bugs. The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.
Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the Effect running once (as in production) and a setup → cleanup → setup sequence (as you’d see in development).
/* As a second example, an API call inside an useEffect with fetch: */
useEffect(() => {
const abortController = new AbortController();
const fetchUser = async () => {
try {
const res = await fetch("/api/user/", {
signal: abortController.signal,
});
const data = await res.json();
} catch (error) {
if (error.name !== "AbortError") {
/* Logic for non-aborted error handling goes here. */
}
}
};
fetchUser();
/*
Abort the request as it isn't needed anymore, the component being
unmounted. It helps avoid, among other things, the well-known "can't
perform a React state update on an unmounted component" warning.
*/
return () => abortController.abort();
}, []);
You can’t “undo” a network request that already happened, but your cleanup function should ensure that the fetch that’s not relevant anymore does not keep affecting your application.
In development, you will see two fetches in the Network tab. There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned... So even though there is an extra request, it won’t affect the state thanks to the abort.
In production, there will only be one request. If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:
function TodoList() {
const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`);
// ...
Update: Looking back at this post, slightly wiser, please do not do this.
Use a ref or make a custom hook without one.
import type { DependencyList, EffectCallback } from 'react';
import { useEffect } from 'react';
const useClassicEffect = import.meta.env.PROD
? useEffect
: (effect: EffectCallback, deps?: DependencyList) => {
useEffect(() => {
let subscribed = true;
let unsub: void | (() => void);
queueMicrotask(() => {
if (subscribed) {
unsub = effect();
}
});
return () => {
subscribed = false;
unsub?.();
};
}, deps);
};
export default useClassicEffect;

How to implement promise when updating a state in functional React (when using useState hooks)

I know similar questions are bouncing around the web for quite some time but I still struggle to find a decision for my case.
Now I use functional React with hooks. What I need in this case is to set a state and AFTER the state was set THEN to start the next block of code, maybe like React with classes works:
this.setState({
someStateFlag: true
}, () => { // then:
this.someMethod(); // start this method AFTER someStateFlag was updated
});
Here I have created a playground sandbox that demonstrates the issue:
https://codesandbox.io/s/alertdialog-demo-material-ui-forked-6zss6q?file=/demo.tsx
Please push the button to get the confirmation dialog opened. Then confirm with "YES!" and notice the lag. This lag occurs because the loading data method starts before the close dialog flag in state was updated.
const fireTask = () => {
setOpen(false); // async
setResult(fetchHugeData()); // starts immediately
};
What I need to achieve is maybe something like using a promise:
const fireTask = () => {
setOpen(false).then(() => {
setResult(fetchHugeData());
});
};
Because the order in my case is important. I need to have dialog closed first (to avoid the lag) and then get the method fired.
And by the way, what would be your approach to implement a loading effect with MUI Backdrop and CircularProgress in this app?
The this.setState callback alternative for React hooks is basically the useEffect hook.
It is a "built-in" React hook which accepts a callback as it's first parameter and then runs it every time the value of any of it's dependencies changes.
The second argument for the hook is the array of dependencies.
Example:
import { useEffect } from 'react';
const fireTask = () => {
setOpen(false);
};
useEffect(() => {
if (open) {
return;
}
setResult(fetchHugeData());
}, [open]);
In other words, setResult would run every time the value of open changes,
and only after it has finished changing and a render has occurred.
We use a simple if statement to allow our code to run only when open is false.
Check the documentation for more info.
Here is how I managed to resolve the problem with additional dependency in state:
https://codesandbox.io/s/alertdialog-demo-material-ui-forked-gevciu?file=/demo.tsx
Thanks to all that helped.

useEffect with empty array dependency is called twice [duplicate]

I have a counter and a console.log() in an useEffect to log every change in my state, but the useEffect is getting called two times on mount. I am using React 18. Here is a CodeSandbox of my project and the code below:
import { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(5);
useEffect(() => {
console.log("rendered", count);
}, [count]);
return (
<div>
<h1> Counter </h1>
<div> {count} </div>
<button onClick={() => setCount(count + 1)}> click to increase </button>
</div>
);
};
export default Counter;
useEffect being called twice on mount is normal since React 18 when you are in development with StrictMode. Here is an overview of what they say in the documentation:
In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React will support remounting trees using the same component state used before unmounting.
This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects do not properly clean up subscriptions in the destroy callback, or implicitly assume they are only mounted or destroyed once.
To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.
This only applies to development mode, production behavior is unchanged.
It seems weird, but in the end, it's so we write better React code, bug-free, aligned with current guidelines, and compatible with future versions, by caching HTTP requests, and using the cleanup function whenever having two calls is an issue. Here is an example:
/* Having a setInterval inside an useEffect: */
import { useEffect, useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((count) => count + 1), 1000);
/*
Make sure I clear the interval when the component is unmounted,
otherwise, I get weird behavior with StrictMode,
helps prevent memory leak issues.
*/
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
};
export default Counter;
In this very detailed article called Synchronizing with Effects, React team explains useEffect as never before and says about an example:
This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs. From the user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, and then pressing Back. React verifies that your components don’t break this principle by remounting them once in development.
For your specific use case, you can leave it as it's without any concern. And you shouldn't try to use those technics with useRef and if statements in useEffect to make it fire once, or remove StrictMode, because as you can read on the documentation:
React intentionally remounts your components in development to help you find bugs. The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.
Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the Effect running once (as in production) and a setup → cleanup → setup sequence (as you’d see in development).
/* As a second example, an API call inside an useEffect with fetch: */
useEffect(() => {
const abortController = new AbortController();
const fetchUser = async () => {
try {
const res = await fetch("/api/user/", {
signal: abortController.signal,
});
const data = await res.json();
} catch (error) {
if (error.name !== "AbortError") {
/* Logic for non-aborted error handling goes here. */
}
}
};
fetchUser();
/*
Abort the request as it isn't needed anymore, the component being
unmounted. It helps avoid, among other things, the well-known "can't
perform a React state update on an unmounted component" warning.
*/
return () => abortController.abort();
}, []);
You can’t “undo” a network request that already happened, but your cleanup function should ensure that the fetch that’s not relevant anymore does not keep affecting your application.
In development, you will see two fetches in the Network tab. There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned... So even though there is an extra request, it won’t affect the state thanks to the abort.
In production, there will only be one request. If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:
function TodoList() {
const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`);
// ...
Update: Looking back at this post, slightly wiser, please do not do this.
Use a ref or make a custom hook without one.
import type { DependencyList, EffectCallback } from 'react';
import { useEffect } from 'react';
const useClassicEffect = import.meta.env.PROD
? useEffect
: (effect: EffectCallback, deps?: DependencyList) => {
useEffect(() => {
let subscribed = true;
let unsub: void | (() => void);
queueMicrotask(() => {
if (subscribed) {
unsub = effect();
}
});
return () => {
subscribed = false;
unsub?.();
};
}, deps);
};
export default useClassicEffect;

React Redux: Useeffect calling multiple times

I am trying to add click even listener to div based class name, here the code looks,
useEffect(() => {
const element = document.querySelectorAll('some-class');
element[0].addEventListener('click',(e)=>{
If(isTrigger)
{
dispatch(setIsTrigger(false))
}
else
{
dispatch(setIsTrigger(true))
}
}, [isTrigger])
Here trying collapse and expand div class based isTrigger value. But its calling multiple times, how to avoid it?
Can some one please help
I am going to take redux out of the equation for a minute. Instead of redux lets assume you have this above the snippet you posted:
[isTrigger, setIsTrigger] = useState(false);
The effect is updating isTrigger, which is a dependency of the effect. This will cause it to go into a loop. Look below at your effect without redux:
useEffect(() => {
const element = document.querySelectorAll('some-class');
element[0].addEventListener('click',(e)=>{
if(isTrigger)
{
setIsTrigger(false)
}
else {
setIsTrigger(true)
}
}, [isTrigger]) // since its a dependency here, its going to get called everytime
// isTrigger is updated. Since your updating it in the function, you get a loop
Hope this helps explain the multiple times.
As for the solving collapse/expand, do you really need the effect? Not sure how your code looks like or your redux state(I see you are using dispatch), but a possible solution:
const isTrigger = useSelector(selectIsTrigger);
handleClick = () => {
dispatch(setIsTrigger(!isTrigger));
};
return (
... some stuff
<div click={handleClick}></div>
)
I hope this helps a little at least.
Also just a note, you should also clean up after your effect since you are adding an event listener. Effects with Cleanup. You should do this when your component unmounts. By cleaning up, you'll avoid listening to events multiple times and memory leaks.
example:
useEffect(() => {
document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, [handleClick]);

React renders without actually changing the state when setInterval is used

I'm going to introduce my question in two steps with slightly different code blocks in both.
Step 1:
Below we have a React application which renders itself every two seconds and therefore causes the browser to print render to the console. If the user presses any key, the renders will stop which in turn stops the console prints. Please ignore the line commented out for now.
import { useState, useEffect, useRef } from 'react';
function App() {
const [number, setUpdate] = useState(0);
const [isPaused, setIsPaused] = useState(false);
const intervalRef = useRef(undefined);
useEffect(() => {
intervalRef.current = setInterval(() => setUpdate(prevNumber => ++prevNumber), 2000);
window.addEventListener('keydown', handleKeyDown);
}, []);
const handleKeyDown = () => {
clearInterval(intervalRef.current);
console.log('console log here');
// setIsPaused(isPaused);
};
console.log('render');
return null;
};
export default App;
Here is a screenshot of the application:
What has happened above, is that I've let the component render five times and then I've pressed a key to stop the component from rendering.
Step 2:
In step 2 we have exactly the same application with the exception of not commenting out the state set in handleKeyDown.
const handleKeyDown = () => {
clearInterval(intervalRef.current);
console.log('console log here');
// This is no longer commented out. Why does it cause a new render?
setIsPaused(isPaused);
};
Here is a screenshot of the application with the code change made in step 2:
Again, I've let the component to render five times after I've pressed a key. But now there is an extra render even though the state should not be changing (because the state is not actually mutating because we set the same value as was already in the state) by setIsPaused(isPaused).
I'm having difficulty to understand what might the reason to cause the extra render at step 2. Maybe it's something obvious?
setIsPaused(isPaused) never causes a new render if I comment out the other state change which is run by setInterval which makes me even more baffled.
This is a known quirk, see #17474. It’s a side effect introduced by the new concurrent mode. The component function did re-run, but DOM will remain untampered.
I also found people post this interesting example. You can try exp with the code. The component function contains something like <div>{Math.random()}</div> although random number did changed in that extra re-run of the function, it wouldn’t reflect onto DOM if state isn’t changed.
Conclusion. You can consider this side effect harmless most of time.
U̶p̶d̶a̶t̶i̶n̶g̶ ̶a̶ ̶s̶t̶a̶t̶e̶ ̶n̶e̶v̶e̶r̶ ̶m̶e̶a̶n̶s̶ ̶D̶O̶M̶ ̶w̶i̶l̶l̶ ̶r̶e̶r̶e̶n̶d̶e̶r̶,̶ ̶y̶o̶u̶ ̶a̶r̶e̶ ̶r̶i̶g̶h̶t̶ ̶̶s̶e̶t̶I̶s̶P̶a̶u̶s̶e̶d̶(̶i̶s̶P̶a̶u̶s̶e̶d̶)̶̶ ̶w̶i̶l̶l̶ ̶n̶o̶t̶ ̶r̶e̶r̶e̶n̶d̶e̶r̶ ̶t̶h̶e̶ ̶D̶O̶M̶,̶ ̶b̶u̶t̶ ̶i̶t̶ ̶w̶i̶l̶l̶ ̶s̶e̶t̶ ̶t̶h̶e̶ ̶s̶t̶a̶t̶e̶ ̶a̶n̶d̶ ̶w̶i̶l̶l̶ ̶g̶i̶v̶e̶ ̶y̶o̶u̶ ̶t̶h̶e̶ ̶u̶p̶d̶a̶t̶e̶d̶ ̶v̶a̶l̶u̶e̶.̶ ̶(̶I̶ ̶a̶m̶ ̶c̶o̶n̶s̶i̶d̶e̶r̶i̶n̶g̶ ̶o̶n̶l̶y̶ ̶s̶t̶a̶t̶e̶ ̶w̶i̶t̶h̶ ̶f̶i̶e̶l̶d̶ ̶̶i̶s̶P̶a̶u̶s̶e̶d̶̶ ̶n̶o̶t̶h̶i̶n̶g̶ ̶e̶l̶s̶e̶)̶
Update: I did know this behavior existed until reading the accepted answer.

Resources