We have updated the content of our program. To access the current Software Engineering curriculum visit curriculum.turing.edu.
Starting Up Redux
Agenda
Redux is a difficult concept to explain. In today’s lesson, we’ll first discuss the Redux data flow, and how this is used alongside React, at a high level. Redux involves a lot of terms that you’re unfamiliar with, but at it’s core, it’s just built from functions and objects.
After our discussion as a group, we’ll get our hands dirty, actually writing an application that uses Redux. Don’t worry, this isn’t the last Redux lesson we’re going to teach.
Hold on to your butts.
Learning Goals
- Know what an action is, and what an action creator is
- Know what a reducer is, and how an action is sent to it
- Have an idea of what the store is, and how it relates to state
- Know what Provider is
- Know what mapStateToProps and mapDispatchToProps are
- Know what
connect
is, and how to use it
Vocab
- Action
- Action Creator
- Dispatch
- Reducer
- combineReducers
- Store
- createStore
- Provider
- context
- mapStateToProps
- mapDispatchToProps
- connect
- container
Draw the 30,000 foot view
The most difficult thing about Redux is understanding what all of the various parts are for. We’re going to go through this one step at a time, but let’s first see where we’re headed. The diagram below is the whole flow, starting with the users, and moving towards the actions. Don’t worry if you don’t understand each step yet, this is just your first time seeing this after all.
As we’re going over each piece of this diagram in class, draw your own version, and make sure to take some notes on what each piece is for.
Code Along
Getting Started
-
Clone this repo
-
npm install
While We NPM Install
In a nutshell, Redux is a predictable state manager for your app. Another way of saying this is that Redux gives you a single source of truth for the data flowing through your application.
When an application fires up, it has an initial state.
Every action that is dispatched after the application launches will describe some kind of change to the application state, and the reducers will make this change happen.
As an application scales, maintaining state across components, an making sure everything that needs to trickle down to the great-great-great-great-grandchild components becomes much less fun. This is where Redux comes in.
Redux is an approach to managing data that adds a ton of complexity to a clientside app in order to make state management more predictable.
You don’t always need Redux. But you should always know what it can do, because when you’re working on a large application, Redux will be your best friend.
Installing Dependencies
Although Redux pairs very nicely with React, it’s not explicitly required to use
them together. Redux can easily be used with a number of other front end
frameworks. For that reason, we need the adapter library react-redux
, as well
as redux
itself.
Install redux and react-redux: npm i -S redux react-redux
Organizing An App
Implementing Redux forces you to think about your app from a high-level perspective, in terms of the data you need to manage and how the state of your app will change.
Let’s start by looking at how to organize a directory structure in broad terms. It is common to put actions, components, containers, and reducers in their own folders. The boilerplate you’ve cloned down should have a structure that looks like this (with some additional files in there, probably):
node_modules
public/
src/
actions/
components/
containers/
reducers/
.gitignore
package.json
README.md
At this point, we know about this much of the Redux process:
Let’s see if we can start to fill in some gaps.
What We Are Building:
Let’s take a second to break this down into components.
What components do you foresee being necessary? What actions? How are these going to be organized?
Components
- Add Todo Form
- List of Todos
- Todo Item
- Filter(s)
- FilterMenu
Let’s break down what our information each component might need to render, and what events (aka “actions”) it needs to listen for.
<AddTodoForm />
State:
‘all todos’
(we need this information because we will figure out the ID of a new todo based on length of old todos)
Actions
“ADD_TODO”
We have a form with a submit button, on click we need it to grab the value from the input field and assign itself a unique ID.
In vanilla React, this would probably be a “handleClick” method that gets sent back up to the parent App component.
<TodoList />
State
Needs to have a list of all todos
Actions
“TOGGLE_TODO”
Each individual todo-list item will have an on-click event that can mark itself as complete. When this happens, we need to update the todo list item.
<Filter />
State
- Which Filter has been selected (ie: all, complete, incomplete)
Actions
“SET_FILTER”
Tells other components which filter has been selected, and therefore which todo list items to display. This means we need it to know which filter is active and update state accordingly.
You Do: Think About It
We know how to build this application with React, right?
Take 5 minutes to turn to the person next to you and talk through how you might implement this simple todo app?
Step Two: Creating Some Actions
So based on our thorough planning, we have three actions: “ADD_TODO”, “TOGGLE_TODO”, and “SET_FILTER” that need to happen for our app to work.
In Redux language - I would say something like:
This means we need three Action Creator functions to return the objects our reducers need to update state.
Out of that statement there are a couple of terms we don’t know. I will define exactly one of them: Action Creator
.
An Action Creator is a function that takes in data from a DOM event, and returns an action object with any additional information that is needed.
We will put those actions in a folder called ‘actions’.
Let’s update our actions/index.js
file to include these action creators.
// src/actions/index.js
export const addTodo = (text, id) => {
return {
type: 'ADD_TODO',
text,
id
}
}
export const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id
}
}
export const setFilter = (filter) => {
return {
type: "SET_FILTER",
filter
}
}
You Do: Think About It
Take a few minutes to turn to the person next to you and see if you can come up with theories on what these functions might be doing.
Don’t peek below, eh?
I Do: What Does It Do?
So here is a brief explanation of what an action creator is:
Action Creators
Functions. They receive data from a DOM event and return a specific action formatted as a JSON object. Think of Action Creators like component organizers - when an event fires, they gather and organize any extra information needed to make changes to state and pass it on in a neat little bundle to the reducer.
We now have this much of the Redux Lifecycle.
Step Three: Creating Some Reducers
So, if you may think of our actions as little sanitizers for expected actions on the website.
When an action happens, we need something that responds to the action and changes the application state accordingly.
So if we were to think about our three actions AddTodo
, ToggleTodo
, setFilter
.
What two nouns do we have?
-
Todo
-
Filter
Okay, cool - create two empty files
touch src/reducers/todos-reducer.js
touch src/reducers/filter-reducer.js
Now think about the following pseudo-code:
I need a function that I can give an action and the state of all todos
Depending on the action, it will 'switch' between different behaviors
If the action is to add a todo:
- It will create a new todo
- Return an array with all todos including the new todo
If the action is to toggle todos
- It change the status of completed on the todo with the matching id
- It will return all the todos in state including the toggled todo
If it doesn't recognize the action, it will return the state unmodified.
This is what something like that might look like:
// src/reducers/todos-reducer.js
const todos = (state=[], action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, {id: action.id, text: action.text, completed: false}]
case 'TOGGLE_TODO':
return state.map(todo => {
if (todo.id !== action.id) {
return todo
}
return Object.assign({}, todo, {completed: !todo.completed})
})
default:
return state
}
}
export default todos
Copy this code over to the appropriate file.
You Do: Play Around with the Code
The src/reducers/todos-reducer.js
file just contains a function. We can run it without React or Redux.
Let’s do that.
Comment out export default todos
for a second.
Make sure you are at your root directory.
In your terminal, type in node
This will get you into the node REPL.
Within the REPL, type:
.load src/reducers/todos-reducer.js
Now, try playing around with the todos function. Pass it different states and different actions and see what it returns.
See if you can get the following things to happen:
- You can add a ‘new todo’
- You can add a few new todos and have them update ‘state’
- You can change an existing todo
Try to break things and get errors.
MAKE SURE to comment your export default todos
back in when you are done.
Wrapping Up Reducers: Filter and Combine
We mentioned that we have one other ‘noun’ to cover with a reducer: filter.
We can do that by adding the following code to our filter-reducer:
// src/reducers/filter-reducer.js
const filter = (state='SHOW_ALL', action) => {
switch (action.type) {
case 'SET_FILTER':
return action.filter
default:
return state
}
}
export default filter
Now that we have two reducers, we are going to need to use something in redux called combineReducers
and export all of our reducers at once?
Why?
Well - we need a root reducer to kind of listen to all possible actions? We can only export one reducer? It’s cleaner this way?
Let’s go with ‘because I said so’ for now….
Add the following code to your project:
// src/reducers/index.js
import { combineReducers } from 'redux'
import todosReducer from './todos-reducer'
import filterReducer from './filter-reducer'
const rootReducer = combineReducers({
todosReducer,
filterReducer
})
export default rootReducer
I Do: What Does It Do?
Reducers
Functions. Takes existing state from the Redux store, pulls in our bundle of information sent from the Action Creators and returns a new state that gets updated in the Redux store. Redux then passes that new state to any components that need to know about it which triggers the React engine to re-render the component.
We have now seen about this much of the Redux Lifecycle.
Step Four: Creating a Component
Here’s our list of all the components we need to build out our application:
Components
- Add Todo Form
- List of Todos
- Todo Item
- Filter(s)
- FilterMenu
In redux, we are going to treat any component that interacts with state differently that other components.
Which components need to interact with and/or care about state?
- Add Todo Form
- We will need to add a new todo to our state based on user input
- Todo List
- Needs access to all todos and any filter information
- Filters
- Need to know which filter is applied, and maybe what that means for our todo list.
For each component that needs to interact with state, we need a container component wrapped around it to talk to our redux store, and a presentational component connected to it that will render the information.
<AddTodo />
Presenter
Let’s create the Presentational Component
first - because it will seem the most familiar to us.
Create a component:
touch src/components/AddTodoForm.js
Now ponder the following psudocode:
First, I will import React and Component
Then I will create a stateful component that extends Component by creating an AddTodoForm class.
My class will have a constructor, which accepts props as an argument and passes props up through super.
I will also define the state as an object, which only contains a key of 'text' with a value of an empty string ('').
I will then define the function render()
Within the render function, I want to set variables for handleSubmit and todos, and assign them to props of the same name.
I then want to return a parent html 'section' element.
Within that section, I want the following html elements:
- form
- input
- button
I want my form to have an onSubmit - which is an anonymous function that calls preventDefault on the event and then calls handleSubmit with the button's text (from state) and the length of all todos.
I want my input to have a value of the text (from state), a placeholder of 'Add a Todo', and an onChange - which is an anonymous function that sets the state of text to the event's target's value.
I want my button to simply include the text AddTodo
Finally, I want to export my component.
You Do: Based on PseudoCode, try to Implement
Without peeking at the code below, take some time to try and create the component that was pseudocoded above.
Don’t try to finish it, just try to parse the confusing pseudocode into a regular old React component.
I Do: Implementing
The code that ultimately makes up the Presenter layer of the AddTodoForm will look like this code. Go ahead and add this in to your codebase.
We only need an input field and a submit button for this example, but you’ll notice we are using a tiny bit of local state to create a controlled component.
The handleSubmit
callback and todos
will be coming in as a prop from the container component that hasn’t been written yet.
<AddTodo />
Container
As previously mentioned, our stateful components need to be wrapped in a ‘container’. That container wraps around a component and is responsible for handing it any information it needs in order to render.
touch src/containers/AddTodoFormContainer.js
In pseudo-code, what we need to do is:
Import the 'connect' method from redux
Import the AddTodo action function
Import the AddTodoForm presenter component
Create one function 'mapStateToProps' responsible for bringing in the props in state.
This function takes state as an argument and returns an object - wherein it creates a key for todos and assigns the todos in state as the value.
Create another function 'mapDispatchToProps' which accepts an argument representing a 'dispatch'. It will return an object which has a key of 'handleSubmit' with a value of an anonymous function.
The anonymous function accepts text and id, and passes it to the addTodo action which is passed to the dispatch.
We then export the result of passing mapStateToProps and mapDispatchToProps and then passing AddTodoForm to that result.
Whew. What?
Containers act as the middle men between the Redux store that knows about state, and the presentation component ready to render it on the page.
Think of a container as a type of shell that is always talking to the redux store
. It wraps around a component to hand it any information it needs to render.
Using Redux’s built in methods connect
and dispatch
it will act as a liaison between the store (which knows about state) and the component (which does not). This is also where the two methods mapDispatchToProps
, and mapStateToProps
come in.
If it seems like a lot of extra code to accomplish something pretty simple - well… it is.
And this is what it should look like
// src/containers/AddTodoFormContainer.js
import { connect } from 'react-redux'
import { addTodo } from '../actions'
import AddTodoForm from '../components/AddTodoForm'
const mapStateToProps = (state) => {
return { todos: state.todosReducer }
}
const mapDispatchToProps=(dispatch) => {
return {
handleSubmit: (text, id) => {
dispatch(addTodo(text, id))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AddTodoForm)
Take a second to notice that the prop handleSubmit
is exactly what our AddTodoForm
component is expecting when it renders. Because of Redux, our container is hooked up to the required action that passes the new component information through our reducers to update state.
Let’s keep going and wire the app up.
You Do: Wiring it Up
Without peeking below, try to predict and add what you think you might need to add the todo form to the main
- Do you need to include the presenter and the container?
- Do you need to pass any props?
- What all will you need to import into
Note: You will get errors even if you get it right, because we are missing one last piece of the redux puzzle.
I Do:
Let’s wire our AddTodoForm component and container up to the DOM through <App />
- the existing file just needs a bit of tweaking.
Here is the actual code needed
Because redux is attaching state to each component directly, we no longer need App to know about or care about the overarching state of our app. It can be a clean functional component.
Also notice that instead of rendering the component, we are rendering the CONTAINER (which, at the end of the day, is wrapped around our presentational form component).
We have now see this much of the redux puzzle
Step Five: Creating a Store
index.js
We need a file that will tell our app what to render to the DOM, pulling in all the necessary pieces. In that file we can create our Redux Store. The file already exists as the entry point of our app (src/index.js
), but we need to beef it up.
// src/index.js
import './index.css'
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from '../src/reducers'
import App from './components/App'
const store = createStore(rootReducer)
render(
<Provider store={ store } >
<App />
</Provider>,
document.getElementById('root')
)
Pop over to your browser and watch the magic happen.
Dig into your React dev tools to check out the nested Connect component!
Redux Dev Tools
Like with React, there is a Chrome dev tools extension to help us look into what Redux is doing behind the scenes.
Go here for the redux dev tool extension
If you install the tools and fire them up immediately, you’ll get a helpful error message.
No store found. Make sure to follow the instructions.
Here are the instructions.
Basically, we need to tell our app to add this extra step as part of the “middleware” of our app. So in between all of the interactions (components –> containers –> actions –> reducers) send the information through these dev tools first to keep us posted.
For now, we will set up our tools like so:
// src/index.js
// ... IMPORT LINES UP HERE
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
const store = createStore(rootReducer, devTools);
// ... RENDER DOWN HERE
Now check out your redux dev tools. Woohoo!
We have now implemented the entire data flow of redux.
Step Six: What Did It All Mean?
So we implemented a redux workflow, but there are likely quite a few questions that are still outstanding.
When To Use Redux
According to this article, Dan Abramov has said:
…don’t use Redux until you have problems with vanilla React.
Redux should be used when you have a significant amount of data changing over time and it is no longer reasonable to keep your state in a top-level React component.
That being said, Redux comes with trade offs. It requires a significant amount of work to set up the structure necessary to implement Redux, as we’ll see shortly, but then once in place allows you to scale your app horizontally.
READ THIS AT SOME POINT: You Might Not Need Redux
Think about adding more components to an app using React without Redux - as the app continues to grow, state from a parent component will need to continually be passed deeper down a vertical chain of nested components. With Redux, the app stops growing vertically the moment it is in implemented and instead allows for a shallow communication of state to any component in the app.
LifeCycle of a Redux App
You Do: Research Spike
-
Stop And Read: ( 10 Minutes ) Data Flow
-
Independent Research ( 10 Minutes )
Spend the next 10 minutes understanding your assigned concept below and jotting down notes - NOTEBOOKS ONLY,
1: Store and Reducers
2: Actions & Provider
3: Presentational vs Container Components
- Single Concept Group Discussion ( 5 - 10 Minutes)
Get together with classmates who also studied the same Redux concept (ie: all 1’s get together, all 2’s get together etc).- Discuss what you understood from your reading and compare notes.
- What role does the section you studied play?
- How does it fit into the data-flow of redux in the bigger-picture?
- Data Flow Group Discussion ( 5 - 10 Minutes)
Head to the white board with your project group. Each member of the group should write their part of the redux data-flow on the board and explain to the rest of the group what it does, then pass the marker to the next person in the data-flow.
Redux Key Concepts
Store
The essence of Redux. Holds all of your application’s state(s) and data in a giant JavaScript object.
Actions
Objects. Recall that every component will do at least two things. It will render itself to the DOM after dealing with necessary data in “State”, and it will potentially interact with user interaction through “Actions”. Every action has a type, and a payload of information that gets sent to the store using store.dispatch()
.
{
type: 'ADD_TODO'
text: 'Get a pumpkin'
}
Action Creators
Functions. They receive data from a DOM event and return a specific action formatted as a JSON object. Think of Action Creators like component organizers - when an event fires, they gather and organize any extra information needed to make changes to state and pass it on in a neat little bundle to the reducer.
const addTodo = (text) => {
type: 'ADD_TODO',
text: text
}
Reducers
Functions. Takes existing state from the Redux store, pulls in our bundle of information sent from the Action Creators and returns a new state that gets updated in the Redux store. Redux then passes that new state to any components that need to know about it which triggers the React engine to re-render the component.
const todos = (state=[], action) => {
switch (action.type) {
case 'ADD_TODO':
// return state with an additional ToDo
case 'TOGGLE_TODO':
// return a version of state based on what filter is applied
default:
// return a default state if an error is made
return state
}
}
Container vs Presentational Components
Container Components
Synonymous with smart, or stateful components. These are parent components to Presentational Components that are connected to Redux and state. Containers pass data to the presentational component, making the connection between the Redux store and the presentational components that need to be rendered.
Presentational Components
Synonymous with a dumb or stateless component. Presentational components receive data from container/smart/stateful components and render themselves accordingly.
Dan Abramov Talks about Presentational vs Container Components
Connect
connect()
Connects a React component to the Redux store. Behind the scenes it actually returns a new connected component that wraps around any existing components.
Takes at least one argument, either mapStateToProps
or mapDispatchToProps
or both.
mapStateToProps(state, [ownProps])
Function.
If passed into connect()
, the component will subscribe to Redux store updates. Any time the store is updated, mapStateToProps
will be called and will pass along the updated props. If ownProps
is specified, its value will be what is passed as props to the component.
mapDispatchToProps(dispatch, [ownProps])
Object or Function.
By default injects the dispatch()
method into your component’s props, which connects any event listeners to a designated action and reducer. If ownProps
is specified, its value will be what is passed as props to the component. If an object is passed as an argument, anything inside will be assumed to be a Redux action creator.
containers/ComponentThatCaresContainer.js
import ComponentThatCares from './ComponentThatCares.js'
const mapStateToProps = (state) => {
// Some code to connect Component to State within the Redux Store
}
const mapDispatchToProps = (dispatch) => {
// Some code to connect Component to app Actions and therefore Reducers within Redux Store
}
export default connect(mapStateToProps, mapDispatchToProps)(ComponentThatCares)
<Provider store={store}>
Typically, this component is wrapped around your application’s root component. This allows the connect()
method in nested components to have access to the Redux store.
Example::
render (
<Provider store={store}>
<App />
</Provider>,
document.getElementById('application')
)
Middleware
Redux Docs: Middleware
Middleware is code inserted between the part of your app receiving a request and the part that generates a response. In other words (straight from the docs), it provides a third party extension point between dispatching an action and the moment it reaches the reducer. There are tons of examples of middleware usage in a React-Redux app, but a big reason is to make asynchronous API calls to fetch data from outside your app.
Example:
(hitTwitterAPI
is a made-up function that makes a request to some third party API, for instance.)
let store = createStore(
reducers,
applyMiddleware(hitTwitterApi)
)
Step Seven: Wrapping Up the App
Challenge yourself to try and finish out the remaining components by copying the first example and using what you already know about React.
Remember, only components needing to interact with state actually need container components. Present components won’t look all that different that what you’re already used to seeing.
If you want to finish up the app functionality, you can finish things up here.
Resources
The original redux diagram used throughout this lesson is courtesy of The New Boston