1

For context, I have a web app that displays an image in a React-Bootstrap Container component (Arena) that holds an image where users are to look and find specific characters.

Separately, I created a div component (CustomCursor) where the background is set to a magnifying glass SVG image.

The Arena component tracks mouse position through an OnMouseMove handler function (handleMouseMove) and passes those coordinates as props to the CustomCursor component.

Here is my Arena component code:

import { useState, useEffect } from 'react';
import { Container, Spinner } from 'react-bootstrap';
import CustomCursor from '../CustomCursor/CustomCursor';
import Choices from '../Choices/Choices';
import { getImageURL } from '../../helpers/storHelpers';

import './Arena.scss';

export default function Arena(props) {

    const [arenaURL, setArenaURL] = useState('');
    const [loaded, setLoaded] = useState(false);
    const [clicked, setClicked] = useState(false);
    const [x, setX] = useState(0);
    const [y, setY] = useState(0);

    function handleClick(e) {
        setClicked(true);
    }

    function handleMouseMove(e) {
        setX(prevState => { return e.clientX });
        setY(prevState => { return e.clientY });
    }

    useEffect(() => {
        retreiveArena();

        // FUNCTION DEFINITIONS 
        async function retreiveArena() {
            const url = await getImageURL('maps', 'the-garden-of-earthly-delights.jpg');
            setArenaURL(url);
            setLoaded(true);
        }
    }, [])

    return (
        <Container as='main' fluid id='arena' className='d-flex flex-grow-1 justify-content-center align-items-center' onClick={handleClick}>
            {!loaded &&
                <Spinner animation="border" variant="danger" />
            }
            {loaded &&
                <img src={arenaURL} alt='The Garden of Earthly Delights triptych' className='arena-image' onMouseMove={handleMouseMove} />
            }
            {clicked &&
                <Choices x={x} y={y} />
            }
            <CustomCursor x={x} y={y} />
        </Container>
    )
}

Here is my CustomCursor code:

import './CustomCursor.scss';

export default function CustomCursor(props) {

    const { x, y } = props;

    return (
        <div className='custom-cursor' style={{ left: `${x - 64}px`, top: `${y + 50}px` }} />
    )
}

When I first created the OnMouseMove handler function I simply set the x and y state values by passing them into their respective state setter functions directly:

    function handleMouseMove(e) {
        setX(e.clientX);
        setY(e.clientY);
    }

However, I noticed this was slow and laggy and when I refactored this function to use setter functions instead it was much faster (what I wanted):

    function handleMouseMove(e) {
        setX(prevState => { return e.clientX });
        setY(prevState => { return e.clientY });
    }

Before:

enter image description here

After:

enter image description here

Why are using setter functions faster than passing in values directly?

Drew Daniels
  • 314
  • 4
  • 16

1 Answers1

1

This is interesting. First of all, we need to focus on reacts way of updating state. In the documentation of react https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous There you can see: React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. For example, this code may fail to update the counter:

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

A pretty good article on this is written by Jan Hesters here: https://medium.com/@jan.hesters/updater-functions-in-reacts-setstate-63c7c162b16a

And more details here: https://learn.co/lessons/react-updating-state

  • 1
    Thanks for the links and reply - read the article above and saw Dan Abramov's tweet about how setter functions should always be used whenever you are relying most recent state in later calls to setState within the same enclosing scope. It makes me wonder if there is ever not a time you would want to not use the prevState => newState setter function form. – Drew Daniels Apr 25 '22 at 13:15
  • Yes, we can directly use setState(). A good example might be receiving response from async server calls. You can check api calls with async await. As far as I understand, using updater functions won't make performance better in that case as API calls itself is in async. – Md Wahidul Azam Apr 25 '22 at 16:52
  • 1
    I think, I should be more specific. Let's assume, we need to make a server call to get some data only once in a page and we don't care when it gets updated, then we can directly use setState to overwrite the current state. This won't make much performance issues. But, if we want to call an api frequently to update data, then it's better to use updater function. But, as react itself recommends to use updater function, let's follow it as much as we can. – Md Wahidul Azam Apr 25 '22 at 17:16