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:
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/.
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.
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.
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.
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.
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.
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
);
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
.
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.
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
;
}
};
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.
3.138.61.107