2

I have an object state inside my react component set up like this:

const [state, setState] = useState({ keyOne: true, keyTwo: 'someKey' })

When I change the state, the component obviously rerenders. But that's the problem. The component is rerendering even if I change the state to the value that it's currently set to. For example, the component still rerenders if I do this:

setState({ keyOne: true, keyTwo: 'someKey' });

I'm guessing this is because I'm passing a new object to setState, and so it will be interpreted as a different state each time even if the content in the object is the same. So I guess my question is:

How can I prevent a component from re-rendering if the new object state has the same content as the original state? Is there a built-in way to do this, or will I have to compare the objects beforehand myself?

2 Answers 2

6

It's rerendering because the new object is a different object from the previous object. It may have all the same properties inside of it, but react only compares using Object.is (which is similar to ===)

If you want to skip the render, you will need to make sure the object reference does not change. For example:

setState(prev => {
  if (prev.keyOne === true && prev.keyTwo === 'someKey')  {
    return prev; // Same object, so render is skipped
  } else {
    return { keyOne: true, keyTwo: 'someKey' };
  }
});

If you have some favorite library for checking deep equality (Eg, lodash or ramda), you could shorten it to something like the following. This will be particularly useful if there are a lot of properties you would otherwise need to check:

setState(prev => {
  const next = { keyOne: true, keyTwo: 'someKey' };
  return R.equals(prev, next) ? prev : next;
});
Sign up to request clarification or add additional context in comments.

1 Comment

Darn, that's what I figured. Oh well, that does make sense. Thanks for the answer, I'll accept it when the site lets me.
1

One option is to use separate states for the different properties of the object. That way, a re-render will only be triggered once one of the new state values is different. For a small example:

const App = () => {
    console.log('rendering');
    const [val, setVal] = React.useState(true);
    React.useEffect(() => {
        setVal(true);
    }, []);
    return 'foo';
};

ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>

As you can see, despite the state setter being called, the component doesn't re-render, because the new state is the same as the old state.

For your code, you'd have something like

const [keyOne, setKeyOne] = useState(true);
const [keyTwo, setKeyTwo] = useState('someKey');

Using an approach like the above instead of putting state into a single object is a very common practice with functional components.

4 Comments

That makes sense. I was considering creating new states for the object fields, but I wasn't sure if that was good practice or not. Thank you for clarifying this
The only problem I would have with this is if I need to change both states to a different value at the same time. Then the component would rerender twice when I only need it to rerender once.
No, if you call multiple state setters at once, React is usually smart enough to wait until you've finished doing all of it before rerendering (and then only does so once). The exception is when inside non-React functions.
Oh, good to know! I would definitely do something like this then if I have an object with few fields.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.