Building React Applications with Idiomatic Redux

Notes for
Building React Applications with Idiomatic Redux

Redux #1: Simplifying the Arrow Functions

If the arrow function only contains a single return statement, you can replace the body of the arrow function with just that value. If this value is an object, don’t forget to wrap it in parentheses so that the parser understands that this is an expression and not a block.

Also, it can be nicer to use the concise method notation, instead of arrow functions because it’s shorter.

Redux #2: Supplying the Initial State

When you create the Redux store, it’s initial state is determined by the root reducer.

const todos = (state=[], action) => {...}
const filters = (state='SHOW_ALL', action) => {...}
const todoApp = combineReducers({
  todos,
  filters
})
const store = createStore(todoApp)
console.log(store.getState())

However, sometimes we want to load some existing data into the app synchronously before it starts. For example, we might have persistent todos from the previous session, and we might want to load this slice of the state into the app right before it starts.

const persistedState = {
  todos: [{
  id: '0',
  text: 'Welcome back!',
  completed: false
  }]
}
const store = createStore(
  todoApp,
  persistedState
)

Redux #3: Persisting the State to the Local Storage

I’m going to write a function called loadState and a new model that I’m going to call localStorage.js that is going to use the localStorage browser API.

in localStorage.js

export const loadState = () => {
  try {
    const serializedState = localStorage.getItem('state')
    if (serializedState === null) return
    return JSON.parse(serializedState)
  } catch (err) {
    return
  }
}
export const saveState = (state) => {
  try {
    const serializedState = JSON.stringify(state)
    localStorage.setItem('state', serializedState)
  } catch(err) {
    // ignore err
  }
}

The loadState function is going to look into localStorage by key, retrieve a string, and try to parse it as JSON. It’s important that we wrap this code into try/catch because calls to your localStorage .getItem can fail if the user privacy mode does not allow the use of localStorage.

import { loadState, saveState } from './localStorage'
import throttle from 'lodash/throttle'
const persistedState = loadState()
store.subscribe(throttle(() => {
  saveState({
    todos: store.getState().todos
  })
}, 1000))
// Wrapping callback in a throttle call insures that the inner function I pass is not going to be called more often than the number of milliseconds I specify.

However, the current code has a bug. If I add a new todo to the existing todos, it’s not going to appear, and react is going to log a warning saying that I encountered two children with the same key, zero. That’s because the todo ID we assigned in the addTodo action creator uses a local variable called nextTodoId as a counter. It’s supposed to be unique. However if the application runs the second time the nextTodoId is going to be initialized to zero again so the new todo which is added also has an ID of zero just like the very first todo.

To avoid problems like this I’m going to install an npm module called node-uuid. It is a very tiny module, and it exports a couple of functions. The function we’re going to use is called v4, which is just a name of the standard. It generates a unique string ID every time, and we’re going to use this ID instead of a counter.

in actions/index.js

import {v4} from 'node-uuid'
export const addTodo = (text) => ({
  type: 'ADD_TODO',
  id: v4(),
  text
})

Redux #4: Refactoring the Entry Point

I’m extracting the logic necessary to createStore, and to subscribe to it to persist the state into a separate file. I’m calling this file configureStore.js, and so I’m creating a function called configureStore that is going to contain this logic so that the app doesn’t have to know exactly how the store is created and if we have any subscribe handlers on it. It can just use the returned store in the index.js file.

It is useful to export configureStore rather than store itself, so that if you later write some tests, you can easily create as many store instances as you want.

in configureStore.js

import { createStore } from 'redux'
import { Provider } from 'react-redux'
import throttle from 'lodash/throttle'
import theApp from './reducers'
import { loadState, saveState } from './localState'

const configureStore = () => {
  const persistedState = loadState()
  const store = createStore(
    theApp,
    persistedState
  )
  store.subscribe(throttle(() => {
    saveState({
      todos: store.getState().todos
    })
  }, 1000))
  return store
}
export default configureStore

I also want to extract the root rendered element into a separate component that I’m going to call Root. It’s going to accept the store as a prop, and it’s going to be defined in a separate file in the components folder.

in root.js

import React, { PropTypes } from 'react'
import { Provider } from 'react-redux'
import App from './App'

const Root = ({store}) => (
  <Provider store={store}>
    <App />
  </Provider>
)
Root.propTypes = {
  store: PropTypes.object.isRequired
}
export default Root

Redux #5: Adding React Router to the Project

in root.js

import React, { PropTypes } from 'react'
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import App from './App'

const Root = ({store}) => {
  <Provider store={store}>
    <Router history={browserHistory}>
      <Route path='/' component={App} />
    </Router>
  </Provider>
}

The links that control the visibility filter do not currently behave like real links. I’d like to change it so that the back button works and the current URL updates when I click on these links and change the current visibilityFilter.

I will add a parameter to my route called filter, and I need to wrap it in parentheses to tell react-router that it’s optional, because if it’s not specified, I want to show all todos.

in root.js

const Root = ({store}) => {
  <Provider store={store}>
    <Router history={browserHistory}>
      <Router path='/(:filter)' component={App} />
    </Router>
  </Provider>
}

in components/Footer.js

import React from 'react'
import FilterLink from '../containers/FilterLink'
const Footer = () => (
  <p>
    Show: {' '}
    <FilterLink filter="all">All</FilterLink>{", "}
    <FilterLink filter="active">Active</FilterLink>{", "}
    <FilterLink filter="completed">Completed</FilterLink>
  </p>
)

in containers/FilterLink.js

import { connect } from 'react-redux'
import { Link } from 'react-router'

const FilterLink = ({filter, children}) => (
  <Link
    to={ filter === 'all' ? '' : filter}
    activeClassName="active">
    {children}
  </Link>
)
// or using `activeStyle` instead

Now I can remove the setVisibilityFilter Action creator, as I don’t use it anymore. I can also remove my custom Component, because now I’m using the {Link} from react-router instead of it.

I’m using “active” and “completed” for the filter prop to align more closely with the paths I want to be displayed, and to avoid passing an empty string, I’ll just use a null string to signify the default path.

Redux 7: Filtering Redux State with React Router Params

in containers/ListTodos.js

const mapStateToProps = (state, ownProps) => ({
  todos: getVisibilityTodos(state.todos, ownProps.filter)
})

I am adding an argument called ownProps to the mapStateToProps function, and I’m going to read the current visibilityFilter from ownProps. I will also change the getVisibilityTodos function to use the current convention we use for the filter prop that is “all”, “completed” or “active”.

The <ListTodos/> component gets rendered from the <App/>, so this is where we need to add the filter prop to make it available in the mapStateToProps function of the <ListTodos/>.

We want the filter prop to correspond to the current filter parameter in our route configuration. react-router makes such parameters available to the route handler components in a special prop called params, so I’m adding a params prop to the app, and now I can read the filter from params.filter. Since it’s empty on the root path, I’m passing all as a fallback to the <ListTodos/>.

in App.js

const App = ({params}) => (
  <div>
    <AddTodo />
    <ListFilter/>
    <ListTodos filter={params.filter || 'all'} />
  </div>
)

Now that the visibilityFilter is managed by react-router, I no longer need the visibilityFilter reducer.

Let’s recap how react-router became the source of truth for the visibilityFilter. In the root component, I render the router, and I only have a single route with an optional filter parameter. The filter parameter gets passed into the <App/> component by react-router, and react-router will make it available inside a special params prop.

I am passing the filter param from the URL, or all if we are on the root path, to the <ListTodos/> component as a filter prop. The <ListTodos/> component now reads the filter from its props and not from the state, so it uses the filter specified by the app.

Finally, I fix the dev server to correctly return index HTML no matter which path is requested, so that React Router can pick it up on the client.

in server.js

app.get("/*", function(req, res) {
  res.sendFile(__dirname + '/index.html')
})

We’re using redux as the source of truth for the todos, and we’re using react-router as the source of truth for anything that can be computed from the URL. In our case, this is the current visibilityFilter.

Redux 8: Using withRouter() to Inject the Params into Connected Components

The <App/> component itself does not really use filter. It just passes the filter down to the <ListTodos/>, which uses it to calculate the currently visible todos. Passing the params from the top level of route handlers gets tedious, so I’m removing the filter prop. Instead, I’m going to find a way to read the current router params in the <ListTodos/> itself.

in containers/ListTodos.js

const mapStateToProps = (state, {params}) => ({
  todos: getVisibilityTodos(state.todos, params.filter || 'all')
})
const ListTodos = withRouter(
  connect(mapStateToProps, mapDispatchToProps)(TodoList)
)

Redux 9: Using mapDispatchToProps() Shorthand Notation

Normally, mapDispatchToProps accepts the dispatch function as an argument so that you can return the props through inject into the React component that each can dispatch certain actions using the dispatch function.

However, it is very common that the arguments passed through the callback props are passed through to the action creators in the same order. In this case, rather than write mapDispatchToProps function yourself, it can pass a configuration object that maps the names of the callback props that we want to inject to the corresponding action creator functions.

in containers/ListTodos.js

// const mapDispatchToProps = (dispatch) => ({
//   toggleTodoStatus: (id) => {
//     dispatch(toggleTodoStatus(id))
//   }
// })
const ListTodos = connect(
  mapStateToProps,
  { toggleTodoStatus }
)(TodoList)

Redux 10: Colocating Selectors with Reducers

in reducers/todos.js

export const getVisibilityTodos = (state, filter) => {
  switch (filter) {
    case 'all':
      return state
    case 'todo':
      return state.filter(t => t.progress === 0)
    case 'doing':
      return state.filter(t => t.progress === 1)
    case 'done':
      return state.filter(t => t.progress === 2)
    default:
      throw new Error(`Unknown filter: ${filter}.`)
  }
}

in reducers/index.js

import todos, * as todoReducer from './todos'
export const getVisibileTodos = (state, filter) =>
  todoReducer.getVisibileTodos(state.todos, filter)

in components/ListTodos.js

import { getVisibileTodos } from '../reducers'
const mapStateToProps = (state, ownProps) => ({
  todos: getVisibileTodos(state, ownProps.filter)
})

Redux 11: Normalizing the State Shape

in reducers/todos.js

import { combineReducers } from 'redux'

const byId = (state={}, action) => {
  switch (action.type) {
    case 'ADD_TODO':
    case 'TOGGLE_TODO':
      return {
        ...state,
        [action.id]: todo(state[action.id], action)
      }
    default:
      return state
  }
}

const allIds = (state=[], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, action.id]
    default:
      return state
  }
}

const todos = combineReducers({
  byId,
  allIds
})

export default todos

const getAllTodos = (state) =>
  state.allIds.map(id => state.byId[id])

export const getVisibileTodos = (state, filter) => {
  const allTodos = getAllTodos(state)
  switch (filter) {
    case 'all':
      return allTodos
    case 'todo':
      return allTodos.filter(t => t.progress === 0)
    case 'doing':
      return allTodos.filter(t => t.progress === 1)
    case 'done':
      return allTodos.filter(t => t.progress === 2)
    default:
      throw new Error(`Unknown filter: ${filter}.`)
  }
}

The new reducer byId is an object. It reads the id of the todo to update from the action and it calls the todo reducer with the previous state of this id and the action.

For the 'ADD_TODO' action, the corresponding todo will not exist in the lookup table yet. We’re calling the todo reducer with undefined as the first argument. The todo reducer would then return a new todo object when handling 'ADD_TODO' so this object will get assigned under the action id key inside the next version of the lookup table.

Then I wrote a second reducer called allIds that manages just the array of ids of the todos. Every time a todo is added, it returns a new array with this id of the new todo at the very end.

I’m combining the two reducers I wrote into a single reducer by calling combineReducers provided by Redux. You may use combineReducers at multiple levels in your reducer hierarchy.

I also wrote the private selector called getAllTodos that just assembles all the todos objects from the state by mapping the ids to the lookup table. For every id, we get the todo from state.byId. I’m returning the array of todos with exactly the same shape as the state inside getsVisibleTodos used to be before this change.

Redux 12: Wrapping dispatch() to Log Actions

in configureStore.js

const addLoggingToDispatch = (store) => {
  const rawDispatch = store.dispatch
  if (!console.group) return rawDispatch
  return (action) => {
    console.group(action.type)
    console.log('prev state', store.getState())
    console.log('action', action)
    const returnValue = rawDispatch(action)
    console.log('next state', store.getState())
    console.groupEnd(action.type)
    return returnValue
  }
}

const storeInitialize = () => {
  // ...
  if(process.env.NODE_ENV !== 'production')
    store.dispatch = addLoggingToDispatch(store)
  // ...
}

Redux 13: Adding a Fake Backend to the Project

const delay = (ms) =>
  new Promise(resolve => setTimeout(resolve, ms))

export const fetchTodos = (state, filter) =>
  delay(500).then(() => {
    switch (filter) {
      case 'all':
        ...
    }
  })

Redux 14: Fetching Data on Route Change

in components/ListTodos.js

import React, { Component } from 'react'
import { fetchTodos } from '../api'

class ListTodos extends Component {
  componentDidMount() {
    this.fetchData()
  }
  componentDidUpdate(prevProps) {
    if (this.props.filter !== prevProps.filter) {
      this.fetchData()
    }
  }
  fetchData() {
    fetchTodos(this.props.filter).then(todos =>
      console.log(this.props.filter, todos)
    )
  }
  render() {
    return <TodoList {...this.props} />
  }
}
ListTodos = connect(
  mapStateToProps,
  actions
)(ListTodos)

Redux 15: Dispatching Actions with the Fetched Data

Redux 16: Wrapping dispatch() to Recognize Promises

in actions/index.js

import * as api from '../api'
const receiveTodos = (filter, response) => ({
  type: 'RECEIVE_TODOS',
  filter,
  response
})
export const fetchTodos = (filter) =>
  api.fetchTodos(filter).then(response =>
    receiveTodos(filter, response)
  )

The fetchTodos action creator calls the fetchTodos function from the ../api but then it transforms its result into a Redux action generated by receiveTodos.

in components/ListTodos.js

import * as actions from '../actions'

class ListTodos extends Component {
  componentDidMount() {
    this.fetchData()
  }
  componentDidUpdate(prevProps) {
    if (this.props.filter !== prevProps.filter) {
      this.fetchData()
    }
  }
  fetchData() {
    const { filter, fetchTodos } = this.props
    fetchTodos(filter)
  }
  render() {
    return <TodoList {...this.props} />
  }
}

in configureStore.js

const addPromiseSupportToDispatch = (store) => {
  const rawDispatch = store.dispatch
  return (action) => {
    if (typeof action.then === 'function')
      return action.then(rawDispatch)
    return rawDispatch(action)
  }
}
const storeInitialize = () => {
  // ...
  if(process.env.NODE_ENV !== 'production')
    store.dispatch = addLoggingToDispatch(store)
  store.dispatch = addPromiseSupportToDispatch(store)
  // ...
}

We override store.dispatch two times. Once to add the logging and second time to add the promise support. The function that adds the promise support accepts the store as an argument and it returns a function that looks like a dispatch function because it accepts the action but if the action is not really an action but a promise, we’re going to wait for that promise to resolve to the real action, which will pass through raw dispatch.

Redux 17: The Middleware Chain

While previous method of extending the store works, it’s not really great at re-override the public api and replace it with custom functions. To get away from this pattern, I am declaring an array of what I’m calling middleware functions, that will be applied later as a single step.

in configureStore.js

const wrapDispatchWithMiddlewares = (store, middlewares) =>
  middlewares.forEach(middleware =>
    store.dispatch = middleware(store)(store.dispatch)
  )

const storeInitialize = () => {
  const persistedState = loadState()
  const middlewares = []
  // ...
  if(process.env.NODE_ENV !== 'production')
    middlewares.push(addLoggingToDispatch)
  middlewares.push(addPromiseSupportToDispatch)

  wrapDispatchWithMiddlewares(store, middlewares)
  // ...
}

Inside the middleware functions themselves, there is a certain pattern that I have to repeat. I’m grabbing the value of store dispatch, and I’m storing it in a variable called rawDispatch so that I can call it later. To make it a part of the middleware contract, I can make rawDispatch an outside argument, just like the store before it and the action after it.

Redux 18: Applying Redux Middleware

$ npm install --save-dev redux-promise
$ npm install --save-dev redux-logger

in configureStore.js

import { createStore, applyMiddleware, compose } from 'redux'
import promise from 'redux-promise'
import createLogger from 'redux-logger'
// ...
const storeInitialize = () => {
  const persistedState = loadState()
  const middlewares = [promise]
  if(process.env.NODE_ENV !== 'production')
    middlewares.push(createLogger())

  const store = createStore(reducer, persistedState, compose(
    applyMiddleware(...middlewares),
    window.devToolsExtension ? window.devToolsExtension() : f => f
  ))
  // ...
}

Redux 19: Updating the State with the Fetched Data

in components/ListTodos.js


Redux 20: Refactoring the Reducers

in reducers/index.js


import { combineReducers } from 'redux'
import byId, * as fromById from './byId'
import createList, * as fromList from './createList'

const listByFilter = combineReducers({
  all: createList('all'),
  todo: createList('todo'),
  doing: createList('doing'),
  done: createList('done')
})

const todos = combineReducers({
  byId,
  listByFilter
})
export default todos

export const getVisibileTodos = (state, filter) => {
  const ids = fromList.getIds(state.listByFilter[filter])
  return ids.map(id => fromById.getTodo(state.byId, id))
}

in reducers/byIds.js

import { combineReducers } from 'redux'

const byId = (state={}, action) => {
  switch (action.type) {
    case 'RECEIVE_TODOS':
      const nextState = {...state}
      action.response.forEach(todo => {
        nextState[todo.id] = todo
      })
      return nextState
    default:
      return state
  }
}
export default byId

export const getTodo = (state, id) => state[id]

in reducers/createList.js

const createList = (filter) => (state=[], action) => {
  if (action.filter !== filter) return state
  switch(action.type) {
    case 'RECEIVE_TODOS':
      return action.response.map(t => t.id)
    default:
      return state
  }
}
export default createList

export const getIds = (state) => state

The todos reducer is now the root reducer of the application, and its file has been renamed to index.js. We extracted the byId reducer into a separate file, and it is exported from this module as a default export. To encapsulate the knowledge about the state shape in this file, we export a new selector that just gets the todo by its id from the lookup table.

We also created a new function called createList that we use to generate the reducers, managing the lists of fetched todos for any given filter. It takes filter as an argument, and it returns a reducer that manages the fetched ids for this filter. The generated reducers will handle the 'RECEIVE_TODOS' action, but they will keep any action that has the filter different from the one they were created with.

createList is the default export from this file, but we also export a selector that gets the ids from the current state. Right now, the ids are the current state, but we are free to change this in the future.

In index.js, we use the namespace import syntax to grab all selectors from the corresponding file into an object. The listByFilter reducer combines the reducers generated by createList, and it uses the filters as the keys.

Redux does not enforce that you encapsulate the knowledge about the state shape in particular reducer files. However, it’s a nice pattern, because it lets you change the state that is stored by reducers without having to change your components or your tests if you use selectors together with reducers in your tests.

Redux 21: Displaying Loading Indicators

in actions/index.js

export const requestTodos = (filter) => ({
  type: 'REQUEST_TODOS',
  filter
})

in reducers/index.js

export const getIsFetching = (state, filter) =>
  fromList.getIsFetching(state.listByFilter[filter])

in reducers/createList.js

import { combineReducers } from 'redux'
const createList = (filter) => {
  const ids = (state=[], action) => {
    if (action.filter !== filter) return state
    switch(action.type) {
      case 'RECEIVE_TODOS':
        return action.response.map(t => t.id)
      default:
        return state
    }
  }
  const isFetching = (state=false, action) => {
    if (action.filter !== filter) return state
    switch(action.type) {
      case 'REQUEST_TODOS':
        return true
      case 'RECEIVE_TODOS':
        return false
      default:
        return state
    }
  }
  return combineReducers({
    ids, isFetching
  })
}
export default createList
export const getIds = (state) => state.ids
export const getIsFetching = (state) => state.isFetching

in containers/ListTodos.js

class ListTodos extends Component {
  // ...
  fetchData() {
    const { filter, requestTodos, fetchTodos } = this.props
    requestTodos(filter)
    fetchTodos(filter)
  }
  render() {
    const { todos, isFetching, toggleTodoStatus } = this.props
    if (isFetching && !todos.length)
      return <p>Loading...</p>
    return <TodoList todos={todos} toggleTodoStatus={toggleTodoStatus} />
  }
}

Redux 22: Dispatching Actions Asynchronously with Thunks

It would be great if I could make requestTodos dispatched automatically when I dispatch fetchTodos because I never want to fire them separately.

in actions/index.js

export const fetchTodos = (filter) => (dispatch) => {
  dispatch(requestTodos(filter))
  return api.fetchTodos(filter).then(response => {
    dispatch(receiveTodos(filter, response))
  })
}

in configureStore.js

// import promise from 'redux-promise'
// ...
const thunk = (store) => (next) => (action) =>
  typeof action === 'function' ?
    action(store.dispatch) :
    next(action)

const storeInitialize = () => {
  const persistedState = loadState()
  const middlewares = [thunk]
  // ...

The new middleware I’m writing is called the thunk middleware because it supports the dispatching of thunks, and it takes the store, the next middleware, and the action as current arguments, just like any other middleware.

If the action is not an action, but rather a function, we’re going to assume that this is a thunk that wants the dispatch function to be injected into it, so I’m calling the action with store.dispatch. Otherwise, I’m just going to return the result of passing the action to the next middleware in chain.

Redux 23: Avoiding Race Conditions with Thunks

in configureStore.js

import thunk from 'redux-thunk'
// ...

in actions/index.js

export const fetchTodos = (filter) => (dispatch, getState) => {
  if (getIsFetching(getState(), filter))
    return Promise.resolve()

  dispatch(requestTodos(filter))
  return api.fetchTodos(filter).then(response => {
    dispatch(receiveTodos(filter, response))
  })
}

Redux 24: Displaying Error Messages

in actions/index.js

export const fetchTodos = (filter) => (dispatch, getState) => {
  if (getIsFetching(getState(), filter))
    return Promise.resolve()

  dispatch({
    type: 'FETCH_TODOS_REQUEST',
    filter
  })

  return api.fetchTodos(filter).then(
    response => {
      dispatch({
        type: 'FETCH_TODOS_SUCCESS',
        filter,
        response
      })
    },
    error => {
      dispatch({
        type: 'FETCH_TODOS_FAILURE',
        filter,
        message: error.message || 'Something went wrong.'
      })
    }
  )
}

in containers/ListTodos.js

import FetchError from './FetchError'
class ListTodos extends Component {
  // ...
  render() {
    const { todos, isFetching, errorMessage, toggleTodoStatus } = this.props
    if (isFetching && !todos.length)
      return <p>Loading...</p>
    if (errorMessage && !todos.length)
      return <FetchError message={errorMessage}
               onRetry={() => this.fetchData()} />
    return <TodoList todos={todos} toggleTodoStatus={toggleTodoStatus} />
  }
}

in actions/createList.js

const createList = (filter) => {
  // ...
  const errorMessage = (state=null, action) => {
    if (action.filter !== filter) return state
    switch(action.type) {
      case 'FETCH_TODOS_FAILURE':
        return action.message
      case 'FETCH_TODOS_REQUEST':
      case 'FETCH_TODOS_SUCCESS':
        return null
      default:
        return state
    }
  }

  return combineReducers({
    ids, isFetching, errorMessage
  })
}

in actions/index.js

export const getErrorMessage = (state, filter) =>
  fromList.getErrorMessage(state.listByFilter[filter])

Redux 25: Creating Data on the Server

in actions/index.js

export const addTodo = (text) => (dispatch) =>
  api.addTodo(text).then(response => {
    dispatch({
      type: 'ADD_TODO_SUCCESS',
      response
    })
  })

in reducers/byId.js

const byId = (state={}, action) => {
  switch (action.type) {
    case 'FETCH_TODOS_SUCCESS':
      const nextState = {...state}
      action.response.forEach(todo => {
        nextState[todo.id] = todo
      })
      return nextState
    case 'ADD_TODO_SUCCESS':
      return {
        ...state,
        [action.response.id]: action.response
      }
    default:
      return state
  }
}

in reducers/createList.js

const createList = (filter) => {
  const ids = (state=[], action) => {
    switch(action.type) {
      case 'FETCH_TODOS_SUCCESS':
        return filter === action.filter ?
          action.response.map(t => t.id) : state
      case 'ADD_TODO_SUCCESS':
        return [...state, action.response.id]
      default:
        return state
    }
  }

Redux 26: Normalizing API Responses with normalizr

in actions/schema.js

import { Schema, arrayOf } from 'normalizr'

export const todo = new Schema('todos')
export const arrayOfTodos = arrayOf(todo)

in actions/index.js

import { normalize } from 'normalizr'
import * as schema from './schema'

export const fetchTodos = (filter) => (dispatch, getState) => {
  // ...
  return api.fetchTodos(filter).then(
    response => {
      dispatch({
        type: 'FETCH_TODOS_SUCCESS',
        filter,
        response: normalize(response, schema.arrayOfTodos)
      })
    },
    // ...
  )
}
export const addTodo = (text) => (dispatch) =>
  api.addTodo(text).then(response => {
    dispatch({
      type: 'ADD_TODO_SUCCESS',
      response: normalize(response, schema.todo)
    })
  })

in reducers/byId.js

const byId = (state={}, action) => {
  if (action.response)
    return {
      ...state,
      ...action.response.entities.todos
    }

  return state
}

in reducers/createList.js

const createList = (filter) => {
  const ids = (state=[], action) => {
    switch(action.type) {
      case 'FETCH_TODOS_SUCCESS':
        return filter === action.filter ?
          action.response.result : state
      case 'ADD_TODO_SUCCESS':
        return [...state, action.response.result]
      default:
        return state
    }
  }
  // ...
}

Redux 27: Updating Data on the Server

in actions/index.js

export const toggleTodoStatus = (id) => (dispatch) =>
  api.toggleTodo(id).then(response => {
    dispatch({
      type: 'TOGGLE_TODO_SUCCESS',
      response: normalize(response, schema.todo)
    })
  })

in reducers/createList.js

const createList = (filter) => {
  const ids = (state=[], action) => {
    const handleToggle = (state, action) => {
      const { result: toggledId, entities } = action.response
      return state.filter(id => id !== toggledId)
    }
    switch(action.type) {
      // ...
      case 'TOGGLE_TODO_SUCCESS':
        return handleToggle(state, action)
      default:
        return state
    }
  }
}
Written on July 8, 2016
Next Post