Using Reducers

Global data have different problems than local data. In particular, we need to make sure that changes to the global data store happen consistently for all users so that different parts of the application all have access to data in the same state.

We’re going to solve our global data problem by refactoring our data using a JavaScript pattern called a reducer and a related structure called a store.

A reducer is a JavaScript function that takes two arguments. The first argument is an object representing a state. The second argument represents an action to take. The return value is a new state object representing the state of the data after the action is performed. The action is meant to describe the state change in terms of application logic, while the reducer converts that to changes in data.

Let’s look at a simple example in which we will count American coins. The state of the world is the total number of coins and the total value of the coins. A reducer to partially handle this task might look like this:

 const initialState = {count: 0, value: 0}
 
 const reducer = (state, action) {
  switch (action.type) {
  case "AddPenny": {
  return { count: state.count + 1, value: state.value + 1 }
  }
  case "AddNickel": {
  return { count: state.count + 1, value: state.value + 5 }
  }
  // and so on...
  }
 }

Then you’d call this method with something like:

 const withAPenny = reducer(initialState, {type: "AddPenny"})
 const pennyAndNickel = reducer(withAPenny, {type: "AddNickel"})

The idea is that you only change state via the reducer function, which—JavaScript being JavaScript—is a constraint you have to enforce yourself. Each call to the reducer function returns a new instance of the state object that is separate from all the other instances.

This is somewhat more verbose than, you know, not doing this kind of change with a reducer, which obviously raises the question, Why use this structure at all? To be perfectly honest, this is a case where my own taste in software structures clashes a little bit. I find this pattern, as typically implemented in JavaScript, to be verbose. That said, the basic idea of making the central state immutable and only accessible via specific methods is still a good one, and we’ll stick with talking about the pattern, as you are most likely to see it in other JavaScript code.

One problem with a reducer on its own is the possibility that different callers to the reducer might get out of sync. A solution to that is to also maintain a store. For our purposes here, a store is a centralized object that manages access to both a single source of central data and a related reducer. For our coin example, a store might look like this:

 export class CoinStore {
  static state = {count: 0, value: 0}
  static getState(): { return state }
  static dispatch(action) {
  CoinStore.state = reducer(CoinStore.state, action)
  return CoinStore.state
  }
 }

Again, we’re holding off on TypeScript annotations. This code sets up a store with a single point of access:

 CoinStore.dispatch({type: "AddPenny"})
 CoinStore.dispatch({type: "AddNickel"})
 const finalState = CoinStore.getState()

In this case, you know that your return value from dispatch is the current state, but typically you only ask for the state explicitly, otherwise you act on it only through actions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.179.186