Redux is a state manager for Javascript. You can use these patterns to store your state in any Javascript project, not just React. It will help you write consistent behavior running in different environments.

You can learn more about redux at:

https://redux.js.org/

We will be building the Todos store based on the tutorial from the Redux website and including it in our React Material UI Todos app.

Here are the links to the tutorial and full code base.

https://redux.js.org/basics

https://gitlab.com/nthchildconsulting/material-ui-todos

Redux

First we will create a folder called in the directory of our app. This way we can keep everything self contained.

Constants

I like to create a folder called that will hold all of our constants we need throughout our store. Within that folder create a file called that will describe what kind of actions we can perform on our store.

src/redux/constants/ActionTypes.js

export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';

You can see that we will perform three simple actions. One to create a todo, toggle a todo complete, and setting the visibility of what todos have been completed or not.

Actions

Now we will create a folder called that will hold all of the actions that we will perform in our store.

src/redux/actions/TodosActions.js

import * as types from '../constants/ActionTypes';let nextTodoId = 0;
export const addTodo = text => ({
type: types.ADD_TODO,
id: nextTodoId++,
text,
});
export const setVisibilityFilter = filter => ({
type: types.SET_VISIBILITY_FILTER,
filter,
});
export const toggleTodo = id => ({
type: types.TOGGLE_TODO,
id,
});
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SOME_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE',
};

Within the reducer of Redux it will expect a type and any other data needed to perform the action. In this file we setup the data structure that will be sent into the reducer. We have three functions, addTodo, toggleTodo, and setVisibilityFilter. You will be able to use these functions within your app.

Reducers

Reducers are where the implementations of the actions happen. Create a folder called . We will add two reducers to the folder.

src/redux/reducers/TodosReducer.js

import * as types from '../constants/ActionTypes';const TodosReducer = (state = [], action) => {
switch (action.type) {
case types.ADD_TODO:
return [
...state,
{
id: action.id,
text: action.text,
completed: false,
},
];
case types.TOGGLE_TODO:
return state.map(todo => {
return todo.id === action.id
? { ...todo, completed: !todo.completed }
: todo;
});
default:
return state;
}
};
export default TodosReducer;

You can see from the code that we are checking the type of action and returning a brand new state based on the action. When we call addTodo it will create a new todo object and put it on the state. If we call toggleTodo it will map through all the todos and toggle the one we clicked on.

src/redux/reducers/VisibilityFilterReducer.js

import { VisibilityFilters } from '../actions/TodosActions';
import * as types from '../constants/ActionTypes';
const VisibilityFilterReducer = (
state = VisibilityFilters.SHOW_ALL,
action
) => {
switch (action.type) {
case types.SET_VISIBILITY_FILTER:
return action.filter;
default:
return state;
}
};
export default VisibilityFilterReducer;

This reducer will handle the setVisibilityFilter action and just set what will be visible in our todos.

Now lets combine both of our reducers to use in our main store.

src/redux/reducers/index.js

import { combineReducers } from 'redux';
import TodosReducer from './TodosReducer';
import VisibilityFilterReducer from './VisibilityFilterReducer';
export default combineReducers({
todos: TodosReducer,
visibilityFilter: VisibilityFilterReducer,
});

src/redux/store.js

import { applyMiddleware, createStore } from 'redux';
import logger from 'redux-logger';
import thunkMiddleware from 'redux-thunk';
import reducers from './reducers';
export default createStore(reducers, applyMiddleware(thunkMiddleware, logger));

Now we can add in the store into our React app, or whatever app you need to.

src/index.js

import React from 'react';
import App from './containers/App/App';
import { render } from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import './index.css';
import { Provider } from 'react-redux';
import store from './redux/store';
const rootElement = document.getElementById('root');
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
registerServiceWorker();

We have setup in our React app to use a Provider with our redux store. We can then dispatch actions to the store. Here is an example of adding a todo.

src/containers/AddTodo/AddTodo.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addTodo } from '../../redux/actions/TodosActions';
import Input from '@material-ui/core/Input';
import Button from '@material-ui/core/Button';
import AddIcon from '@material-ui/icons/Add';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
const styles = theme => ({
button: {
[theme.breakpoints.down('xs')]: {
width: '35px',
height: '35px',
},
},
});
class AddTodo extends Component {
state = {
todo: '',
};
handleChange = event => {
this.setState({ todo: event.target.value });
};
handleClick = () => {
if (!this.state.todo.trim()) {
return;
}
this.props.dispatch(addTodo(this.state.todo));
this.setState({ todo: '' });
};
render() {
const { classes } = this.props;
return (
<div>
<Grid container spacing={24} justify="center">
<Grid item xs={10} sm={10}>
<Input
fullWidth
placeholder="What will you do next?"
value={this.state.todo}
onChange={this.handleChange}
onKeyPress={e => {
if (e.key === 'Enter') {
e.preventDefault();
this.handleClick();
}
}}
/>
</Grid>
<Grid item xs={2}>
<Button
variant="fab"
color="primary"
aria-label="add"
className={classes.button}
onClick={this.handleClick}
>
<AddIcon />
</Button>
</Grid>
</Grid>
</div>
);
}
}
AddTodo.propTypes = {
classes: PropTypes.object.isRequired,
};
export default connect()(withStyles(styles)(AddTodo));

We can use the redux connect to add the store to our props. When we click on the button to add a todo it will call the handleClick event and dispatch our addTodo action.

Conclusion

There is a bit of boilerplate when using Redux but you can explicitly set up all your actions and easily test all the of functionality of your app. Once you have all your actions coded all you need to do is provide your app with the Redux store and it will be available to all your components.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store