You Might Not Need an Effect has been live for, at the very least, a couple of years now. It has really helped! However, I keep seeing at least one incorrect and widely-spread usage of useEffects: server/local state synchronization.
const Edit = ({ data, onSave }) => {
const [dynamicData, setDynamicData] = useState(data)
useEffect(() => {
setDynamicData(data) // Don't do this!
}, [data])
return (...)
}-
Double rendering:
datachanges => new render =>useEffectruns =>dynamicDatachanges => new render. -
Synchronizing React state to React state is unncessary.
-
Dynamic/local data may be unexpectedly overwritten when/if static/props data changes unexpectedly. Imagine your user is filling a form and your
useQuery'srefetchIntervalstrikes; it would overwrite all the dynamic/local data your user may have input!
Adjusting some state when a prop changes hides the answer to this issue:
Important
Derive your state!
This is how you'd do it:
-
Initialize your local/dynamic state as
null.const Edit = ({ data: staticData }) => { const [dynamicData, setDynamicData] = useState(null) ... }
-
Derive the actual component state:
const Edit = ({ data: staticData, onSave }) => { const [dynamicData, setDynamicData] = useState(null) const data = dynamicData ?? staticData ... }
This allows you to keep your state minimal. The premise is using the local/dynamic data only after there has been interaction, and otherwise use the props/static data. That's why
nullis handy as an initial value. -
Don't forget to clear local/dynamic data as needed.
const Edit = ({ data: staticData, onSave }) => { const [dynamicData, setDynamicData] = useState(null) const data = dynamicData ?? staticData const reinitializeState = () => { setDynamicData(null) } const handleSave = () => { onSave(data).then(reinitializeState) } ... }
- No double rendering:
datachanges, and the actual component state is derived during render.
Note
Since you'll need to reinitializeState after awaiting for onSave's promise, you'll kinda end up with two rerenders - but definitely faster! Technically, they could at least be batched if the state lived in the same component.
-
Deriving state is simpler to reason about; no indirection.
-
Predictably and on-demand reset your dynamic/local data.