user14994547
user14994547

Reputation:

Render Once for Multiple set States using useState hook in React

Is there a way to refresh / render a function component once after updating multiple states?

For example:

const [first, setFirst] = useState(false);
const [second, setSecond] = useState(false);

when called ...

...
setFirst(true);
setSecond(true);

... then the component will refresh twice. Is it possible to set both and refresh (or render) only once?

Upvotes: 9

Views: 5659

Answers (2)

Ajeet Shah
Ajeet Shah

Reputation: 19813

I don't see it rendering twice. It is rendering exactly once because both set states are getting batched together.

function App() {

  const [first, setFirst] = React.useState(false);
  const [second, setSecond] = React.useState(false);
  const counter = React.useRef(1)
  
  console.log('rendering: ', counter.current++)
 
  return (
    <button onClick={() => {
      setFirst(p => !p)
      setSecond(p => !p)
    }}>{first?'t1':'f1'}, {second?'t2':'f2'}</button>)
}

ReactDOM.render(<App />, document.getElementById('mydiv'))
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<body>
<div id="mydiv"></div>
</body>

Maybe, you are seeing it rendering twice as a result of using StrictMode. StrictMode will intentionally double invoke "render" and some other lifecycle methods to detect side-effects.

See Why is React Component render counter getting incremented by 2? for a demo.


Update:

I think you are looking for a solution mentioned in @EvrenK's answer i.e. Merge your state variables.

Normally, in most cases, you don't have to worry too much about rerenders unless it becomes an apparent issue. If and when it becomes noticeable issue / delay, you can avoid renders caused by change in props using Reat.memo.

There is no way, you can avoid renders caused by change in states.

However, there is a hacky way to have more control of your renders: Move all (those few causing the issue) data from state to ref / useRef and create one state data to trigger the final render.

Example:

const data1 = useRef()
const data2 = useRef()
const data3 = useRef()
const [renderD1D2D3, setRenderD1D2D3] = useState(false)

function handleChange1(newValue) {
  data1.current = newValue // Won't trigger render
}
function handleChange2(newValue) {
  data2.current = newValue // Won't trigger render
}
function handleChange3(newValue) {
  data3.current = newValue // Won't trigger render
}

function allGoodLetsRenderNow() {
  setRenderD1D2D3(prev => !prev) // Would trigger render
}

return (
  <>
    <div>{data1.current}</div>
    <div>{data2.current}</div>
    <div>{data3.current}</div>
  </>
)

Upvotes: 1

Evren
Evren

Reputation: 4410

You can use your states as an object and set them in one instead of separete states like

const [exampleState, setExampleState] = useState(
  {fields: {
        fieldOne: false,
        fieldTwo: false
        }
   })

You can set in this way

    setExampleState({...exampleState, fields: {
        fieldOne: true,
        fieldTwo: true
        },
   })

Upvotes: 5

Related Questions