Binding Only Relevant Values (Partial or Computed)
There are cases where a component doesn't need the exact value of the entity, but rather some derivative of this value. It can either be:
- a portion of the value (e.g. for object type values), or
- a computed value
By binding only these relevant values to the component, we prevent unnecessary re-renders. For this purpose, we can pass a transform function to the entity's use
hook as follows:
value = entityObj.use(value => relevantValue)
This transform function, also called selector in other conventions, takes the current entity value, then returns some other value that is more relevant to the component. The hook then binds only this derived value to the component, thus preventing unnecessary re-renders.
Here's an example. Notice that computed values can also depend on preceding values/transforms.
MainView.js
import { settings } from './entities/settings'
import { counter } from './entities/counter'
const RiggedCounter = () => {
// partial value 👇
const extra = settings.use(value => value.extra)
// computed value 👇
const count = counter.use(value => value + extra)
return (
// . . .
)
}
(Due to type inference, the TypeScript version is the same as above.)
Pro tip: If you design your entities to be as atomic as possible, you would less likely need to use transform functions to extract partial values. Applying this concept to the above example, we can break up
settings
and define a standalone entity calledextra
instead, which is just a primitive (number) value. This is just an alternative, though, not necessarily the "better" approach.
Below are some more advanced topics that pertain to optional features that are provided for flexibility.
Using a custom equality function
When deciding whether it should trigger a re-render, use
compares the current vs. previous result of the transform function. The default equality check used is ===
(strict equality), but we can specify a different equality function if needed.
value = entityObj.use(transformFn, equalityFn)
The equalityFn
is expected to come in this form:
(a, b) => boolean
Shallow equality
The library provides shallowEqual
for cases where the transform function returns an object with top-level properties derived from the entity value. In the example below, shallowEqual
returns true
—and therefore the component will not update—if both theme
and enableCountdown
properties of the computed value did not change.
MainView.js
import { shallowEqual } from 'simpler-state'
import { settings } from './entities/settings'
const MainView = () => {
const config = settings.use(value => {
return {
theme: value.theme,
enableCountdown: value.featureFlags.countdown
}
}, shallowEqual)
// 👆
return (
// . . .
)
}
(Due to type inference, the TypeScript version is the same as above.)
Optimization
To further enhance the app's performance, it's always a good idea to memoize the transform and equality functions. We can choose from various techniques, such as:
- defining them outside the component
- placing them alongside the entity and its actions
- using React's
useCallback
hook to keep them inside the component