SimpleR State

The simplest app state management for React

View project on GitHub

Quick Start: TL;DR Cheat Sheet

Setup

npm install simpler-state

Two easy steps!

Step 1: Create an entity (shared state) and actions (updater functions)

// counter.js

import { entity } from 'simpler-state'

export const counter = entity(0)

export const reset = () => {
  counter.set(0)
}

export const increment = by => {
  counter.set(value => value + by)
  // --OR-->  counter.set(counter.get() + by)  
}

Step 2: Use the entity in your components with hooks

import { counter, increment, reset } from 'counter'

const CounterView = () => {
  const count = counter.use()
  // --OR-->  const count = useEntity(counter)

  return (
    <>
      <div>{count}</div>

      <button onClick={() => increment(1)}> + </button> 
      <button onClick={reset}> Reset </button>
    </>
  )
}

(Other components can use the shared counter the same way.)

Using partial and computed values in components

Use only the relevant part of the entity value:

const MainView = () => {
  const theme = settings.use(value => value.theme)
  //                               👆
  return <Layout theme={theme} /> 
}

... or a computed value:

const RiggedCounter = () => {
  const count = counter.use(value => value + 20)
  //                               👆
  return <div>{count}</div>
}

Async actions

Use Promise or async/await for async actions.

export const loadConfig = async () => {
  settings.set({ loading: true, config: null })

  const res = await fetchConfig()
  settings.set({ loading: false, config: res })
}

Async initial value

Use Promise or async function as initial state to prefetch data.

const fetchTopScores = async () => {
  /* Fetch data from server here ... */
  return data
}
//    Initial value is a Promise  👇  (do NOT `await`)
export const topScores = entity(fetchTopScores())

Persistence

Persist entity value to localStorage:

import { entity, persistence } from 'simpler-state'

export const counter = entity(0, [persistence('counter')])

or sessionStorage:

export const counter = entity(0, [
  persistence('counter', { storage: 'session' })
])

or custom storage with (optional) custom serializer and deserializer:

const remoteStorage = {
  getItem: async key => {
    const res = await fetchFromServer(key)
    return res.data
  },
  setItem: async (key, value) => {
    await saveToServer(key, value)
  }
}

export const counter = entity(0, [
  persistence('counter', {
    storage: remoteStorage,
    serializeFn: val => JSON.stringify({ value: val, updated: Date.now() }),
    deserializeFn: res => JSON.parse(res).value
  })
])

Orchestrated updates to multiple entities

Implement an action that updates multiple entities by invoking either their set method directly, or other actions associated with them.

import { counter } from './entities/counter'
import { signOut } from './entities/auth'

export const logout = () => {
  counter.set(0)  // 👈 using an entity's setter
  signOut()  // 👈 using an associated action
}

Simpler nested-object updates

Use object proxy libraries, such as Immer, to simplify nested-object immutable updates.

import produce from 'immer'

export const items = entity([])

export const changeItemLabel = (index, label) => {
  items.set(
    produce(value => {
      value[index].label = label   // 👈 mutation allowed
    })
  )
}

Unit testing of entities

Invoke an action, then use the entity object's get method to inspect the current value. Use the entity's init method to reset its value before each test case.

describe('counter', () => {
  beforeEach(() => {
    counter.init()  // 👈 Reset value
  })

  describe('increment', () => {
    it('increases the value of the counter', () => {
      //   👇 Invoke action
      increment(1)
      expect(counter.get()).toBe(1)
      //              👆 Inspect value
    })
  })

  // . . .
})

Reset all entities between component tests

Reset all entities to their initial value between component tests.

import { resetAll } from 'simpler-state'

describe('CounterView', () => {
  beforeEach(() => {
    resetAll()  // 👈 Reset all entities before each test
  })

  // . . .
})

Back to home | Next topic → The Basics