Dustin Byers
Redux is a predictable state container for JavaScript apps, and a very valuable tool for organizing application state. It’s a popular library to manage state in React apps, but it can be used just as well with Angular, Vue.js or just plain old vanilla JavaScript.
One thing most people find difficult about Redux is knowing when to use it. The bigger and more complex your app gets, the more likely it’s going to be that you’d benefit from using Redux. If you’re starting to work on an app and you anticipate that it’ll grow substantially, it can be a good idea to start out with Redux right off the bat so that as your app changes and scales you can easily implement those changes without refactoring a lot of your existing code.
In this brief introduction to Redux, we’ll go over the main concepts: reducers, actions, action creators and store. It can seem like a complex topic at first glance, but the core concepts are actually pretty straightforward.
A reducer is a pure function that takes the previous state and an action as arguments and returns a new state. Actions are an object with a type and an optional payload:
function myReducer(previousState, action) => {
// use the action type and payload to create a new state based on
// the previous state.
return newState;
}
Reducers specify how the application’s state changes in response to actions that are dispatched to the store.
Since reducers are pure functions, we don’t mutate the arguments given to it, perform API calls, routing transitions or call non-pure functions like Math.random() or Date.now().
If your app has multiple pieces of state, then you can have multiple reducers. For example, each major feature inside your app can have its own reducer. Reducers are concerned only with the value of the state.
Actions are plain JavaScript objects that represent payloads of information that send data from your application to your store. Actions have a type
and an optional payload
.
Most changes in an application that uses Redux start off with an event that is triggered by a user either directly or indirectly. Events such as clicking on a button, selecting an item from a dropdown menu, hovering on a particular element or an AJAX request that just returned some data. Even the initial loading of a page can be an occasion to dispatch an action. Actions are often dispatched using an action creator.
In Redux, an action creator is a function that returns an action object. Action creators can seem like a superfluous step, but they make things more portable and easy to test. The action object returned from an action creator is sent to all of the different reducers in the app.
Depending on what the action is, reducers can choose to return a new version of their piece of state. The newly returned piece of state then gets piped into the application state, which then gets piped back into our React app, which then causes all of our components to re-render.
So lets say a user clicks on a button, we then call an action creator which is a function that returns an action. That action has a type
that describes the type of action that was just triggered.
Here’s an example action creator:
export function addTodo({ task }) {
return {
type: 'ADD_TODO',
payload: {
task,
completed: false
},
}
}
// example returned value:
// {
// type: 'ADD_TODO',
// todo: { task: '🛒 get some milk', completed: false },
// }
And here’s a simple reducer that deals with the action of type ADD_TODO
:
export default function(state = initialState, action) {
switch (action.type) {
case 'ADD_TODO':
const newState = [...state, action.payload];
return newState;
// Deal with more cases like 'TOGGLE_TODO', 'DELETE_TODO',...
default:
return state;
}
}
All the reducers processed the action. Reducers that are not interested in this specific action type just return the same state, and reducers that are interested return a new state. Now all of the components are notified of the changes to the state. Once notified, all of the components will re render with new props:
{
currentTask: { task: '🛒 get some milk', completed: false },
todos: [
{ task: '🛒 get some milk', completed: false },
{ task: '🎷 Practice saxophone', completed: true }
],
}
Redux gives us a function called combineReducers
that performs two tasks:
We keep mentioning the elusive store
but we have yet to talk about what the store actually is.
In Redux, the store
refers to the object that brings actions (that represent what happened) and reducers (that update the state according to those actions) together. There is only a single store in a Redux application.
The store has several duties:
getState()
.dispatch(action)
.subscribe(listener)
.subscribe(listener)
.Basically all we need in order to create a store are reducers. We mentionned combineReducers
to combine several reducers into one. Now, to create a store, we will import combineReducers
and pass it to createStore
:
import { createStore } from 'redux';
import todoReducer from './reducers';
const store = createStore(todoReducer);
Then, we dispatch actions in our app using the store’s dispatch
method like so:
store.dispatch(addTodo({ task: '📖 Read about Redux'}));
store.dispatch(addTodo({ task: '🤔 Think about meaning of life' }));
// ...
One of the many benefits of Redux is that all data in an application follows the same lifecycle pattern. The logic of your app is more predictable and easier to understand, because Redux architecture follows a strict unidirectional data flow.
store.dispatch(actionCreator(payload))
.export default const currentTask(state = {}, action){
// deal with this piece of state
return newState;
};
export default const todos(state = [], action){
// deal with this piece of state
return newState;
};
export default const todoApp = combineReducers({
todos,
currentTask,
});
When an action is emitted todoApp
will call both reducers and combine both sets of results into a single state tree:
return {
todos: nextTodos,
currentTask: nextCurrentTask,
};
That was a lot to go over in very little words, so don’t feel intimidated if you’re still not entirely sure how all the pieces fit together. Redux offers a very powerful pattern for managing application state, so it’s only natural that it takes a little practice to get used to the concepts.
To learn more check out these resources:
🎩 In future posts we’ll explore more advanced topics like dealing with asynchronous events using Redux-Saga or Redux Thunk.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!