Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`useState`, only update component when values in object change

Problem

useState always triggers an update even when the data's values haven't changed.

Here's a working demo of the problem: demo

Background

I'm using the useState hook to update an object and I'm trying to get it to only update when the values in that object change. Because React uses the Object.is comparison algorithm to determine when it should update; objects with equivalent values still cause the component to re-render because they're different objects.

Ex. This component will always re-render even though the value of the payload stays as { foo: 'bar' }

const UseStateWithNewObject = () => {
  const [payload, setPayload] = useState({});
  useEffect(
    () => {
      setInterval(() => {
        setPayload({ foo: 'bar' });
      }, 500);
    },
    [setPayload]
  );

  renderCountNewObject += 1;
  return <h3>A new object, even with the same values, will always cause a render: {renderCountNewObject}</h3>;
};

Question

Is there away that I can implement something like shouldComponentUpdate with hooks to tell react to only re-render my component when the data changes?

like image 613
agconti Avatar asked Jan 13 '20 17:01

agconti


People also ask

What is the difference between usestate and setState in react?

React this.setState, and useState does not make changes directly to the state object. React this.setState, and React.useState create queues for React core to update the state object of a React component.

How to update the state of a React component?

React this.setState, and React.useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That’s why changes don’t feel immediate. Even if you add a setTimeout function, though the timeout will run after some time.

How to manage an object’s state within the object?

To understand how to manage an object’s state, we must update an item’s state within the object. In the following code sample, we’ll create a state object, shopCart, and its setter, setShopCart. shopCart then carries the object’s current state while setShopCart updates the state value of shopCart:

Why is the process to update ReACT state asynchronous?

So the process to update React state is asynchronous for performance reasons. That’s why changes don’t feel immediate. Even if you add a setTimeout function, though the timeout will run after some time.


1 Answers

If I understand well, you are trying to only call setState whenever the new value for the state has changed, thus preventing unnecessary rerenders when it has NOT changed.

If that is the case you can take advantage of the callback form of useState

const [state, setState] = useState({});
setState(prevState => {
  // here check for equality and return prevState if the same
  return {...prevState, ...updatedValues};
});

Here is a custom hook (in TypeScript) that does that for you automatically. It uses isEqual from lodash. But feel free to replace it with whatever equality function you see fit.

import { isEqual } from 'lodash';
import { useState } from 'react';

const useMemoizedState = <T>(initialValue: T): [T, (val: T) => void] => {
  const [state, _setState] = useState<T>(initialValue);

  const setState = (newState: T) => {
    _setState((prev) => {
      if (!isEqual(newState, prev)) {
        return newState;
      } else {
        return prev;
      }
    });
  };

  return [state, setState];
};

export default useMemoizedState;

Usage:

const [value, setValue] = useMemoizedState('some initial value');
like image 53
klugjo Avatar answered Sep 19 '22 05:09

klugjo