I am trying to learn react hooks. I need help in understanding does react functions always reset hook state every time it renders.
Here is a small example of scroll where I am trying to fix the header
class Header extends Component {
constructor(props) {
super(props);
console.log("Constructor")
this.state = {fixed: false};
}
handleScroll = () => {
console.log(window.scrollY);
console.log(this.state.fixed);
if (window.scrollY >= 25 && !this.state.fixed) {
this.setState({
fixed: true
});
} else if(window.scrollY < 25 && this.state.fixed){
this.setState({
fixed: false
});
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("updated ");
}
componentDidMount() {
console.log("added");
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
console.log("removed");
window.removeEventListener('scroll', this.handleScroll);
}
render() {
return (
<div></div>
);
}
}
Output:
Header.js:26 Constructor
Header.js:51 added
Header.js:32 1
Header.js:33 false
Header.js:32 2
Header.js:33 false
Header.js:32 5
Header.js:33 false
Header.js:32 9
Header.js:33 false
Header.js:32 15
Header.js:33 false
Header.js:32 20
Header.js:33 false
Header.js:32 26
Header.js:33 false
Header.js:47 updated
Header.js:32 31
Header.js:33 true
Header.js:32 35
Header.js:33 true
Header.js:32 38
Header.js:33 true
Header.js:32 40
Header.js:33 true
Header.js:32 39
Header.js:33 true
Header.js:32 38
Header.js:33 true
Header.js:32 35
Header.js:33 true
Header.js:32 31
Header.js:33 true
Header.js:32 25
Header.js:33 true
Header.js:32 20
Header.js:33 true
Header.js:47 updated
Header.js:32 9
Header.js:33 false
Header.js:32 5
Header.js:33 false
Header.js:32 2
Header.js:33 false
Header.js:32 0
Header.js:33 false
It works fine.
Now with react hooks same logic. I am just trying to look for the behavior of FixedHeader state.
const Header = props => {
console.log("rendering");
const [FixedHeader, setFixedHeader] = useState(false);
useEffect(() => {
document.addEventListener("scroll", () => {
console.log(window.scrollY);
console.log(FixedHeader);
if (window.scrollY >= 30) {
setFixedHeader(true);
} else {
setFixedHeader(false);
}
})
}, []);
return (
<div></div>
);
}
Output:
Header.js:23 rendering
Header.js:28 1
Header.js:29 false
Header.js:28 2
Header.js:29 false
Header.js:28 5
Header.js:29 false
Header.js:28 9
Header.js:29 false
Header.js:28 15
Header.js:29 false
Header.js:28 20
Header.js:29 false
Header.js:28 26
Header.js:29 false
Header.js:28 31
Header.js:29 false
Header.js:23 rendering
Header.js:28 35
Header.js:29 false
Header.js:23 rendering
Header.js:28 38
Header.js:29 false
Header.js:28 40
Header.js:29 false
Header.js:28 39
Header.js:29 false
Header.js:28 38
Header.js:29 false
Header.js:28 35
Header.js:29 false
Header.js:28 31
Header.js:29 false
Header.js:28 25
Header.js:29 false
Header.js:23 rendering
Header.js:28 20
Header.js:29 false
Header.js:23 rendering
Header.js:28 14
Header.js:29 false
Header.js:28 9
Header.js:29 false
Header.js:28 5
Header.js:29 false
Header.js:28 2
Header.js:29 false
Header.js:28 0
Header.js:29 false
I can't understand this behavior why rendering is called twice, and FixedHeader is false even when scroll is >= 30.
I am new to js so I think this is the way function work rendering is there 2 times because function got called twice but why twice, and each time function is called all the states need to be set again doesn't it going to impact the performance (I may be wrong).
Note: I have not added html part it is simple header where I am adding class "fixed-top" based on the boolean variable.
I can't understand this behavior why rendering is called twice, and FixedHeader is false even when scroll is >= 30.
I guess it is because of closure, the function remember FixedHeader value, which it had in a moment, function was created... you can create some object outside component and write FixedHeader to its property if you really need to see value inside your scroll event callback
const holder = {}
const Header = props => {
const [FixedHeader, setFixedHeader] = useState('false');
holder.value = FixedHeader
const handleScroll = function () {
console.log(window.scrollY)
console.log(holder.value)
if (window.scrollY >= 30) {
setFixedHeader('true');
} else {
setFixedHeader('false');
}
}
useEffect(() => {
document.addEventListener("scroll", handleScroll)
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return (
but because of react does not rerender immidiately sometimes value will not be right
The reason "rendering" is displayed twice is because you are using different conditions.
For the class component you use:
if (window.scrollY >= 25 && !this.state.fixed) {
// ...
} else if (window.scrollY < 25 && this.state.fixed) {
// ...
}
While the function component uses:
if (window.scrollY >= 30) {
// ...
} else {
// ...
}
To fix this issue you need to add the check the current state.
However like you've already noticed checking FixedHeader
value will always result in the same value (it is not getting updated). So we need to tackle that problem first.
The problem is that setFixedHeader
doesn't update the FixedHeader
in the current context. It tells React to re-render using the passed value as the new FixedHeader
on the next Header
call, but FixedHeader
in the current context is never changed.
useEffect
allows you to return a function that handles clean-up. This function runs if the component unmounts, or before the next call of useEffect
(when the dependency list has changed). Adding FixedHeader
to the dependency list will remove the previous scroll event handler (using the returned clean-up function) and adds a new scroll event handler using the new FixedHeader
value when the FixedHeader
value changes.
const {useEffect, useState} = React;
const Header = props => {
console.log("rendering");
const [fixed, setFixed] = useState(false);
useEffect(() => {
const handleScroll = () => {
console.log(window.scrollY);
console.log(fixed);
if (window.scrollY >= 30 && !fixed) {
setFixed(true);
} else if (window.scrollY < 30 && fixed) {
setFixed(false);
}
};
document.addEventListener("scroll", handleScroll);
return () => document.removeEventListener("scroll", handleScroll);
}, [fixed]);
return null;
}
ReactDOM.render(<Header />, document.querySelector("#header-container"));
body { height: 1000px; }
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="header-container"></div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With