Performance comparison between react hooks and react class

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 () {
      if (window.scrollY >= 30) {
      } else {

    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 => {
    const [fixed, setFixed] = useState(false);

    useEffect(() => {
        const handleScroll = () => {
            if (window.scrollY >= 30 && !fixed) {
            } else if (window.scrollY < 30 && fixed) {
        document.addEventListener("scroll", handleScroll);
        return () => document.removeEventListener("scroll", handleScroll);
    }, [fixed]);

    return null;

ReactDOM.render(<Header />, document.querySelector("#header-container"));
body { height: 1000px; }
<script src="[email protected]/umd/react.production.min.js"></script>
<script src="[email protected]/umd/react-dom.production.min.js"></script>
<div id="header-container"></div>