Chapter 3. Redux Patterns

There are a number of patterns worth learning about that are commonly found in Redux applications. These patterns are usually about reducing the amount of code you need to write to perform small, common tasks. Usually these patterns require the help of ES6 to simplify and reduce the amount of code you write. In fact, it’s common to see ES6 heavily used in Redux, so you need to be comfortable reading it.

Note: You can find the complete source code for the sample application at https://github.com/arturmuller/developing-a-redux-edge.

It’s important to really understand these patterns because it will make reading and writing Redux application code much easier. Before we look at them, let’s quickly review the ES6 features that are commonly used to create the patterns:

  • Object Destructuring
  • Object Spread Operator (in-proposal)
  • Array Spread Operator
  • Arrow Functions
  • Computed Property Names

This isn’t an ES6 primer, so we won’t be covering these features in this book. There are, however, plenty of resources available online where you can learn more about them. Instead we’re going to take a look at how these features are commonly used in Redux.

Tip: Use Babel’s online parser to test these examples out. Make sure you understand what’s going on behind the scenes. You can try this out for yourself at https://babeljs.io/repl/.

Improving functions

The following patterns are used to improve the functions that you write. Improvements here usually cover reducing the size of functions. These patterns reduce the amount of code you need to write, while also improving the readability of the functions.

Concise functions

Redux embraces functional programming, which results in more functions that are smaller. Given that you’ll be writing more functions, it’s a good idea to use the best syntax for the job. In Redux, it’s very common to see arrow functions used to make functions more succinct. Consider the following action creator that has been written without the use of arrow functions:

export const removeNote = function (id) {
  return {
    type: 'app/removeNote',
    payload: { id },
  };
}

It’s not bad, but it can be better. Considering how often you’ll write functions like the one above, you want to use the most concise syntax available. Arrow functions allow you to simplify the above example, thus reducing the amount of code that you write. Compare the previous action creator with the following one, which uses arrow functions:

export const removeNote = (id) => {
  return {
    type: 'app/removeNote',
    payload: { id },
  };
}

It’s somewhat better, but at the moment all we’ve really accomplished is the removal of the function keyword. It’s common to take this a step further. If an arrow function only consists of a return statement, you can omit the return statement and make it implicit:

export const removeNote = (id) => ({
  type: 'app/removeNote',
  payload: { id },
})

This is better. In this example we’ve completely removed the function keyword and return statement. Notice that we’ve also wrapped the curly braces in parenthesis too. We had to do this in order to prevent it from being parsed as a block statement. You’ll see this pattern used a lot in Redux, so make sure that you understand what’s going on here.

Extracting parameters

Another type of function you will be writing is a reducer function. These functions are passed an action object as their second argument. This action object is used as a container, and you usually extract a number of properties from it.

Rather than manually extracting object properties into local variables, you can instead use object destructuring. With this pattern you can extract properties in the function signature rather than adding (unnecessary) additional lines of code to your reducers.

For example, consider the following reducer that extracts properties from the action object into local variables:

export const addTodo = (state = [], action) => {
  const type = action.type;
  const todo = action.todo;
  switch (type) {
    case 'addTodo':
      return [...state, todo];
    default:
      return state;
  }
};

This isn’t terrible, but we’ve added boilerplate to the reducer, which is reducing the readability of the reducer. Instead, compare the previous example to the following one, which uses object destructuring to extract these properties:

export const addTodo = (state = [], { type, todo }) => {
  switch (type) {
    case 'addTodo':
      return [...state, todo];
    default:
      return state;
  }
};

This is a big improvement. We’ve removed 2 lines of code from our reducer, which reduces noise and results in a much cleaner function. This is a very common pattern and it’s not unusual to see all reducers follow this approach.

Reducer patterns

Reducers often perform a simple task, such as adding an element to an array or removing a property from an object. At the same time, reducers should take care not to mutate existing state, meaning that they always return new objects or arrays if they need to change them. This fact usually complicates the logic and makes the usual approaches obsolete. The following patterns are commonly found in reducers to help achieve these goals.

Adding elements to arrays

It’s very common to add elements to an array in your reducers. The following pattern is typically used for this purpose.

export const addTodo = (state = [], { type, todo }) => {
  switch (type) {
    case 'addTodo':
      return [...state, todo];
    default:
      return state;
  }
};

The above code uses the array spread operator to add an element to an array. To achieve this without the array spread operator would require creating a duplicate of the array and manually pushing the new element into the array:

    export const addTodo = (state = [], { type, todo }) => {
      switch (type) {
        case 'addTodo':
          const newState = state.slice();
          newState.push(todo);
          return newState;
        default:
          return state;
      }
};

As you can see, the array spread operator reduces the amount of code that we need to write when adding new elements to arrays while avoiding mutations.

Removing elements from arrays

Another common pattern is to remove elements from arrays. There are a few approaches to this problem depending on your use case.

To remove an element from the end of an array you can use the Array.slice method:

const removeLastItem = (items) => {
    return items.slice(0, -1);
}

To remove an element from the front of the array you can use the ES6 array spread feature:

const removeFirstItem = (items) => {
    const [last, ...rest] = items;
    return rest;
}

To remove an element by a predicate you can use Array.filter:

const removeItemById = (items, id) => items.filter(item => item.id !== id);

Changing object properties

The following pattern is typically used in reducers when updating object properties. It allows you to easily change the property of an object while enforcing immutability.

const toggleTodo = (todo) => ({
    ...todo,
    completed: !todo.completed
  });

The above code creates a new object that consists of every property from the existing todo object. We then overwrite the completed property with the value of the todo.completed property inverted. Notice that we’re also using the arrow function pattern mentioned previously.

This pattern also uses the object spread operator, which is currently in- proposal. If you prefer to avoid using the object spread operator, you can use Object.assign as an alternative:

const toggleTodo = (todo) => (Object.assign({}, todo, {
  completed: !todo.completed
}));

Note:Object.assign is part of ES6, but browser support may vary. You may need to include a polyfill if you chose to use Object.assign.

Adding properties to objects

The same pattern can also be used to add new properties to objects. For example:

const addTodo = (todos, todo) => ({
  ...todos,
  [todo.id]: todo
});

The above code adds a new todo to the todos object, using the id of the todo as the property name. You can also use the Object.assign alternative too if you prefer:

const addTodo = (todos, todo) => (Object.assign({}, todos, {
  [todo.id]: todo
}));

The only difference between this and the changing properties on the objects example is that this one is using a computed property name. We only needed to do that here because the todos object is being used as a map.

Removing properties from objects

When an object is used as a map, which is common, there is often logic required to remove properties from the object. This is usually easy to do, but the rules of immutability make this a little more difficult in Redux. The following pattern can be used for this purpose, because it’s a concise solution for removing a property from an object without mutating the original object.

export const removeTodo = (state = {}, { type, id }) => {
  switch (type) {
    case 'removeTodo':
      const {[id]: remove, ...rest} = state;
      return rest;
    default:
      return state;
  }
};

It might be difficult to see what’s going on here at first, because we’re using a combination of object spread, object destructuring, and computed properties.

Basically, we’re extracting a property from the state object by name (using a computed property to provide the name) and storing that value in the remove variable. At the same time, we’re using the object rest to extract the remaining properties (every other property except the one extracted with remove) and storing those as a new object in the rest variable.

Finally, we’re returning the rest variable as the new state object. In the end, the rest variable represents the collection with the specified item removed.

You can achieve the same thing without using ES6 features, but it’s not as concise. We have to manually create a new object, copy the properties over and then delete the properties we no longer require:

export const removeTodo = (state = {}, { type, id }) => {
  switch (type) {
    case 'removeTodo':
      const newState = Object.assign({}, state);
      delete newState[id];
      return newState;
    default:
      return state;
  }
};

Conclusion

We’ve covered many common patterns that you’ll likely run into as you learn and read more about Redux, and the majority of them use ES6 features to help keep the code concise. It is a lot of information to digest, but it’s worth spending the time to familiarize yourself with these patterns and where they are used. In the next chapter we will create our example app, which will put to use the concepts covered thus far.

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

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