A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/whtsky/immer below:

whtsky/immer: Create the next immutable state by mutating the current one

Your personally assistance for creating your next immutable state

Immer (German for: always) is a tiny package that allows you work with immutable state in a more convenient way. It is based on copy-on-write mechanism.

The basic idea is that you will apply all your changes to a draftState. Which is a proxy of the currentState, and once all your mutations are completed, immer will produce the nextState based on the mutations to the draft state. This means that you can interact with your data by simply modifying it, while keeping all the benefits of immutable data.

Using immer is like having a personal assistant; he takes a letter (the current state), and gives you a copy (draft) to jod changes onto. Once you are done the assistant will take your draft and produce the real immutable, final letter for you (the next state).

The immer package exposes a single function:

immer(currentState, fn: (draftState) => void): nextState

const baseState = [
    {
        todo: "Learn typescript",
        done: true
    },
    {
        todo: "Try immer",
        done: false
    }
]

const nextState = immer(baseState, draftState => {
    draftState.push({ todo: "Tweet about it" })
    draftState[1].done = true
})

The interesting thing about immer is that baseState will be untouched, but that nextState will reflect all changes made to draftState.

// the new item is only added to the next state,
// base state is unmodified
expect(baseState.length).toBe(2)
expect(nextState.length).toBe(3)

// same for the changed 'done' prop
expect(baseState[1].done).toBe(false)
expect(nextState[1].done).toBe(true)

// unchanged data is structurally shared
expect(nextState[0]).toBe(baseState[0])
// changed data not (dûh)
expect(nextState[1]).not.toBe(baseState[1])

A lot of words; here is a simple example of what difference that could make in practice. The todo reducers from the official Redux todos-with-undo example

Note, this is just a sample application of the immer package. Immer is design to just simply Redux reducers. It can be used in any context where you have an immutable data tree that you want to clone and modify (with structural sharing)

const todo = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        id: action.id,
        text: action.text,
        completed: false
      }
    case 'TOGGLE_TODO':
      if (state.id !== action.id) {
        return state
      }

      return {
        ...state,
        completed: !state.completed
      }
    default:
      return state
  }
}

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        todo(undefined, action)
      ]
    case 'TOGGLE_TODO':
      return state.map(t =>
        todo(t, action)
      )
    default:
      return state
  }
}

After using immer, that simply becomes:

import immer from 'immer'

const todos = (state = [], action) =>
  // immer produces nextState from draftState and returns it
  immer(state, draftState => {
    switch (action.type) {
      case 'ADD_TODO':
        draftState.push({
          id: action.id,
          text: action.text,
          completed: false
        })
        return
      case 'TOGGLE_TODO':
        const todo = draftState.find(todo => todo.id === action.id)
        todo.completed = !todo.completed
        return
    }
  })

Creating middleware or reducer wrapper that applies immer automatically is left as exercise to the reader :-).


RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4