Reputation:
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
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.
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
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