useState without setter function
Joseph Jang
2022-11-18
We think of useMemo for the memoization technique when we need to preserve a reference over the component render cycle.
Assume that a resource needs to be initialized only once in the app's life cycle. The recommended pattern here is to create instances outside of the component in general.
const resource = new Resource() // ⬅️ A static instance is created only once.
const Component = () => (
<ResourceProvider resource={resource}>
<App />
</ResourceProvider>
)
Resource instance is created only once when bundled and now we can make this instance availiable from any component tree with Context Provider. However, enviroment like micro-frontend, you will need to mount an app mutiple times where each app requires seperate resource instance.
Ok, I think we can move the constructor function inside the component, therefore, each rendering will create new resource instance. This also seems not quite right. If the component is rendered only once, it's fine. But as we all know that managing re-rendering is quite cumbersome.
const Component = () => {
// ⬇️⬇️ useMemo only recomputes the value when the dependency array changes
const resource = React.useMemo(() => new Resource(), [])
return (
<ResourceProvider resource={resource}>
<App />
</ResourceProvider>
)
}
Is there any way to solve this problem? Many developers will think of useMemo first. useMemo only recomputes the value when the dependency array changes, and since Resource has no dependency, it seems like a good appoach. But, Is it really?
👉 let’s see what react doc says about useMemo
💡
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.Here we are more interested in prserving the safety of referential equality rather than performance opimization.
ok, so..no useMemo, then what?
😲 useState without setter
const Component = () => {
// ⬇️⬇️ function is called only once at initial render
const [resource] = React.useState(() => new Resource())
return (
<ResourceProvider resource={resource}>
<App />
</ResourceProvider>
)
}
We know that state is only to be updated when the setter is called. In other word, if we don’t set a new value to the state, we can guaranteed that the resource instance never changes. We can easily avoid calling the setter by not declaring the second value of the returned array. And plus, if you know that lazy initialization, you can ensure that the that the Resource constructor is called only once too. This way you can ensure that Resources are created only once per component lifecycle.
useRef
const Component = () => {
const resource = React.useRef(null)
if (!resource.current) {
resource.current = new Resource()
}
return (
<ResourceProvider resource={resource.current}>
<App />
</ResourceProvider>
)
}
You can achieve the same result using useRef. And it doesn't violate the React rules, and it can maintain the purity of rendering. but personally, this code looks more complicated. What do you think?