Skip to content

Instantly share code, notes, and snippets.

@JamesHagerman
Last active February 7, 2018 01:15
Show Gist options
  • Select an option

  • Save JamesHagerman/b494d49e2f3f092bb3f9c0ce327deafe to your computer and use it in GitHub Desktop.

Select an option

Save JamesHagerman/b494d49e2f3f092bb3f9c0ce327deafe to your computer and use it in GitHub Desktop.
How not to do things wrong while using Redux... grains of salt shall be gargled

Prologue

The point of this document is to attempt to get people to stop shooting themselves in the foot by thinking a bit differently.

In any case, one of the "beautiful things" about the whole React+Redux community is they're totally okay with letting people shoot themselves in the foot. So, have at it, I guess, if you like that sort of thing...

Past Tense

There is contention on the point of treating React Action Types as things that have happened in the past.

Personally, I take these three pieces of information to inform this my point of view in this document:

The Gist

From the Redux doc's Introduction page (https://redux.js.org/#the-gist)[https://redux.js.org/#the-gist]:

The whole state of your app is stored in an object tree inside a single store. The only way to change the state tree is to emit an action, an object describing what happened. To specify how the actions transform the state tree, you write pure reducers.

That's it!

Reducers

From the Redux doc's Reducers page (https://redux.js.org/docs/basics/Reducers.html#reducers)[https://redux.js.org/docs/basics/Reducers.html#reducers]:

Remember that actions only describe the fact that something happened, but don't describe how the application's state changes.

Controversial discussion on Past Tense

Closed issue on the Redux GitHub page: (https://github.com/reactjs/redux/issues/384#issuecomment-126890410)[https://github.com/reactjs/redux/issues/384#issuecomment-126890410]

There is a lot of argument about this, but if the writer of Redux is mostly on board with the idea, that's pretty good. Even better that a lot of people are also behind it.

Understanding Redux

Treat Redux as a Dynastic Scribe! A Court Stenographer! A Drunken Friend!

You wouldn't try to control them... So don't try to control Redux!

Definition of Terms

An Action Creator:

  • Exposes an API to your application that will (most likely) causes state changes
  • After a state change, they dispatch the state change as a Redux Action to the Redux Reducers to inform them that the application state has changed.

A Redux Action:

  • Describes a state change that has ALREADY HAPPENED in the application
  • Describes the state change to the Redux Reducer

A Redux Reducer:

  • Listens for state changes in your application; I.e. Listens for Redux Actions
  • Inspects the current state and the action payload to determine what has changed
  • Decides what changes it cares about based on it's inspections
  • Keeps track of state changes by updating the Redux Store IF IT DECIDES TO DO SO!
  • Does not mutate current state
  • Always returns what it believes to be the current Application State

Wrong way:

The wrong way is to think of Redux Actions as things we WANT REDUX TO DO FOR US. Redux Actions are NOT your API! Your Action Creators are!

Do not try to control Redux! Let it respond to you! Inform it of when things have changed, and let it (and it's Redux Reducers) decide how to keep track of things.

Using Redux Actions as a glorified getter/setter API on the global Redux store object is worse than just defining a global object and mutating it directly! Do not treat it as a global object!

The following example is how not to set things up.

  • The Action Creators are well defined. Good to go there...
  • The Action Types, APPEND_TO_PRESET_LIST, SELECT_PRESET, and DESELECT_PRESET, lead us to believe that Redux will somehow do the work for us. That is a slippery slope to trating Redux as a global object.
  • The Redux Reducers are given a task they shouldn't be doing. They are being told to update the state. They are not allowed to update the state based on what they know of the current state and the action.payload they're being asked to respond to.
// Action Creators
export const saveToPresetsList = (fieldlist = [], name = 'preset_name') => {
  return (dispatch, getState) => {
    const {
      presetData
    } = getState()
    return presetService
      .create({
        name,
        presetData
      })
      .then(payload => dispatch({ type: APPEND_TO_PRESET_LIST, payload }))
  }
}
export const selectPreset = (presetId) => {
  return (dispatch, getState) => {
    dispatch({
      type: SELECT_PRESET,
      payload: presetId
    })
  }
}
export const deselectPreset = () => {
  return (dispatch, getState) => {
    dispatch({
      type: DESELECT_PRESET,
      payload: -1
    })
  }
}

// Reducer that handles those Action Types:
function presetReducer(state = initialState, action) {
  switch (action.type) {
    case APPEND_TO_PRESET_LIST:
      const availablePresets = state.availablePresets.concat(action.payload)
      return {
        ...state,
        availablePresets
      }
    case SELECT_PRESET:
      return Object.assign({}, state, {
        selectedPresetId: action.payload
      })
    case DESELECT_PRESET:
      return Object.assign({}, state, {
        selectedPresetId: -1
      })
    default:
      return state
  }
}

Right Way:

This is mostly right, as far as I'm concerned.

  • The Action Creator API is the same. No real difference as far as the App is concerned...
  • The Action Types define state changes that have already happened by the time Redux hears about it.
    • A PRESET_CREATED state change has happened
      • The PRESET_CREATED Redux Reducer will record the change
    • A PRESET_SELECTION_CHANGED state change has happened
      • The PRESET_SELECTION_CHANGED Redux Reducer will record the change
  • The Redux Reducers are free to respond to state changes based on the current state and action.payload as it learns about the state changes.
// Action Creators
export const saveToPresetsList = (fieldlist = [], name = 'preset_name') => {
  return (dispatch, getState) => {
    const {
      presetData
    } = getState()
    return presetService
      .create({
        name,
        presetData
      })
      .then(payload => dispatch({ type: PRESET_CREATED, payload }))
  }
}
export const selectPreset = (presetId) => {
  return (dispatch) => {
    dispatch({ type: PRESET_SELECTION_CHANGED, payload: presetId })
  }
}
export const deselectPreset = () => {
  return (dispatch) => {
    dispatch({ type: PRESET_SELECTION_CHANGED, payload: -1 })
  }
}


// Reducer that handles those Action Types:
function presetReducer(state = initialState, action) {
  switch (action.type) {
    case PRESET_CREATED:
      const availablePresets = state.availablePresets.concat(action.payload)
      return {
        ...state,
        availablePresets
      }
    case PRESET_SELECTION_CHANGED:
      if (action.payload) {
        return Object.assign({}, state, {selectedPresetIndex: action.payload})
      }
      return Object.assign({}, state, {selectedPresetIndex: -1})
    default:
      return state
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment