Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance comparison between react hooks and react class

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.

like image 791
ooo Avatar asked Sep 21 '20 11:09

ooo


2 Answers

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

like image 140
Dmitry Reutov Avatar answered Nov 03 '22 12:11

Dmitry Reutov


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>
like image 41
3limin4t0r Avatar answered Nov 03 '22 11:11

3limin4t0r