I was trying to make an element position : "fixed" after it scrolls to the end of the screen and whenever it comes back to the first screen it should again get the position as "relative" , but state is changing to true (fixed) when it scrolls to the end of the screen but when it scroll back it is not changing to false (relative).
const [fixed, setFixed] = useState(() => {
return false;
});
const scrollHandler = () => {
if (window.scrollY > window.innerHeight) {
if (!fixed) {
setFixed(true);
}
} else {
if (fixed) {
setFixed(false);
}
}
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, []);
return (
<>
<div
style={{ position: `${fixed ? "fixed" : "relative"}` }}
className={classes.navigationBelt}
></div>
</> );
Here is the code explaination
I have taken a state named as "fixed" and initial Value as "false"
const [fixed, setFixed] = useState(() => {
return false;
});
I have added an eventListener on the window for the scroll, when the component mounts into the dom.
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, []);
For Handling the scroll event I have created a function named as "scrollHandler"
const scrollHandler = () => {
if (window.scrollY > window.innerHeight) {
if (!fixed) {
setFixed(true);
}
} else {
if (fixed) {
setFixed(false);
}
}
};
I am setting the "fixed" to "true" when the scroll is more than the height of the window and making an element to "fixed". Note : I am only updating the state when "fixed" state is false, otherwise it will cause multiple re-render.
return (
<>
<div
style={{ position: `${fixed ? "fixed" : "relative"}` }}
className={classes.navigationBelt}
></div>
</> );
It is working fine, but when it scrolls back and "else" condition hit still the state ("fixed") is showing be false even though it has changed and element has already got fixed. Because of that setFixed(false) does not work as the outer condition is still false.
else {
if (fixed) {
setFixed(false);
}
}
I wanted to know why is this happening?
2
Answers
Because the
scrollHandler
you attach to the window has access to thefixed
variable when it isfalse
(the initial state).You attach it in a
useEffect
that is only run once, so it will always keep that reference of thescrollHandler
.You can pass the
fixed
as a dependency to theuseEffect
and this way it will refresh/update the event handler.The above solution has the "problem" that you need to know what
scrollHandler
does in order to specify/update the dependency list of theuseEffect
.A more sane approach would be to re-run the
useEffect
when thescrollHandler
changes since it is used inside theuseEffect
(and this is the real dependency of thatuseEffect
)But since your code will change the
scrollHandler
handler in each re-render of the component, you should try to make that as "stable" as possible, that is to keep/re-use the same reference for that function while its own dependencies are the same. To do that you can use theuseCallback
and use thefixed
as its dependency.Im not really familiar with react but im wondering if there any problems to do it with a additional ref? against the fact that it is not so elegant, but can also be nice if the useEffect not triggered on page reRender?
With this way, i can also get the right way the initial state.
i’m glad to hear the best use case