SimpleR State

The simplest app state management for React

View project on GitHub

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 called extra 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

Back to home | More recipes...