Part 3 — You’re here now.
My overly-simplified explanation is; A UI element dispatches an action, that action is an object that contains only the relevant information, the action is sent to the reducer, the reducer receives the current state of the app and the action and returns a new state, the UI is subscribed to the state and efficiently updates when said state changes.
We’re talking about Redux, based on the Flux architecture, here is a really good explanation, read it if you’re not already familiar.
We’ll use a constants
file to ensure consistency, eliminate silly syntax errors and help document our code:
Line 7
Since we’ll be using a todoText
more than once we may as well set it up as a const in the scope of the describe
.
Line 10 — 14
This is the action we’re going to expect our App to dispatch when we call the submitTodo
function with our todoText
as the argument. It’s the bare minimum that our Redux reducer
will need in order to store this todo; a type
so the reducer knows what kind of action this is, an id
so it has a unique identifier, and the text
of the todo itself.
Line 16
The assertion.
Our failing test tells us what to build, so let’s create the action:
Line 3
Create a mutable variable to hold our id
.
Line 5 — 8
A nextId
helper method that increments the id
then returns it.
Line 11 — 17
Our action that takes the text
as an argument and returns an object that will be dispatched to our reducer.
Line 15
Just saying text,
is shorthand for text: text,
.
Our test should be passing.
This is where we store and return new states based on actions. Firstly we could test that it returns an initialState
upon instantiation:
Line 3
We import the reducer
itself and its initialState
variable.
Line 7
Assert that when we call the reducer
with no state and an empty action that it properly returns our expected initialState
.
Let’s make our test pass:
Line 1
Export an initialState
module from this file.
Line 3
Export the reducer
, it’s a function that takes state
(defaulting to initialState
) and action
as its arguments and simply returns state
. As this reducer returns new states Redux will keep a track of it and use said new state in future reductions.
Line 5
Export the reducer
by default when we import this file.
Now let’s test that it can receive our submitTodo
action and return a new, correct state:
Line 3
Import our types
constants.
Line 7
Set up a todoText
variable to help with assertion consistency.
Line 15 — 19
Set up the action we expect to pass to our reducer.
Line 21 — 28
Set up the expected return state
, containing an array of todos
, with our brand new todo plonked in to it.
Our test shows us what is wrong, we’re expecting a new state to be returned with the todo we’ve submitted but we’re still just getting an empty initialState
object. So let’s fix it:
Line 1
import the types
module to give us our constants
.
Line 3 — 5
Change our initialState
to include a todos
key which is an empty array on instantiation. This will end up being an array of todo objects.
Line 7
We’ve refactored our reducer to be a function that does more than simply return the initialState
.
Line 8
We use a standard switch
statement to return new computed states based on the action.type
.
Line 10 — 11
In the case that our action type is “SUBMIT_TODO
” we return this object representation of state.
Line 12 — 19
The gist of this syntax is; we want to start by returning whatever exists and then overwrite the new things we want to add, remove or change.
Line 12
Return the unpacked state, that means all the contents of state including the empty todos
array.
Line 13
Overwrite the todos
key with this array.
Line 14
This array contains whatever our current state’s todos
array happens to contain, whether that’s nothing at all or dozens of todo objects.
Line 15 — 18
Now add a new object to this array and give it an id
of the action.id
we pass in, and give it a text
of the action.text
we pass in.
Therefore we’ve just returned a new representation of state that contains the existing state plus the new todo we’ve just submitted. Hope that makes sense.
Line 22 — 23
The default return of our reducer, should no action.type
be present that we can handle, is just the existing state.
Our reducer unit test now passes. So technically our code now knows how to add a todo to the state, now we just need to set up Redux and connect things up through our App
component.
First we need to install redux
and react-redux
as dependencies.
npm install --save redux react-redux
redux
is completely separate from React and is just a way to manage state, a single source of truth for our app.
react-redux
provides us React bindings for redux
.
So let’s create our store:
Line 1
import the combineReducers
and createStore
sub modules from redux
.
Line 2
import our reducer which is capable of returning new states of the store.
Line 4 — 6
Superfluous at this point because we only have one reducer, but as our App scales we will want reducers to have manageable responsibility and would use combineReducers
to combine them in to one for our store to use.
Line 8
We export our store as default. Simple.
Then:
Line 5
import the Provider
module from react-redux
, it’s one purpose is to provide the store to all wrapped child components, in this case our App and all it’s sub components.
React-redux-connect explained.
Line 7
import the store
we created above.
Line 10 — 15
We’ve done two things here; Refactor our code to be slightly more legible, and wrap our App
in our Provider
in which we pass in our reducer and redux-store as the store
.
Line 1
Add an eslint exception for the document
element look up in our render method.
And finally:
Line 2
import connect
so we can expose necessary part of redux to our App.
Line 5
import our actions
we created previously so we can dispatch them in our components.
Line 18
As the name suggests, we’re preparing our state as created in our store.js
to our properties to be consumed by our component.
Line 20
And same again, we’re preparing the dispatchable actions in our App to properties that can be used in our components.
Line 21 — 25
We have created our submitTodo
dispatch event that takes a text
argument and if it’s not falsey it will dispatch our actions.submitTodo
function.
Line 28
The connect
module now finishes the job off, it gives our App access to the only two ways in which it can interact with state; Dispatching actions and subscribing to state. Another interpretation would be; Telling the store how to change and reading the state of it. This now becomes our default export from this file.
Line 3
import PropTypes
for our prop validation.
Line 14 — 16
Validate the submitTodo
function we’re about to pass in.
Line 7
Our connect
has given us access to submitTodo
as a prop so we simply pass it in to our App
. Note: We’ve changed this to not be the default export, the connected App is the default, but we still expose it for our test later.
Line 10
And then we pass it down to the AddTodo
component that needs it.
Finally update the test:
Line 5
We have to go from importing App
to { App }
instead since we want to test this component in isolation without using the entire connected component, which would require we pass in a Provider
and store
.
Line 6
Bring in the initialState
to use as our App’s state.
Line 9
Create a mock function for submitTodo
.
Line 11 — 15
Refactor a little to make the code readable and easier to extend, then consume the state
and submitTodo
.
Here’s a clear and concise explanation of redux and connect.
You will probably also find the react-redux API docs useful, and be amazed at how small and simple it really all is.
We don’t have our todos displaying yet but they can actually be entered via the UI and store in state. Grab the Chrome React devtools extension and type in a todo and then click submit.
Now go and view the store and see it appear:
Storing a todo in state
We’ve learned how to:
Let’s start making the most of being able to subscribe to state and actually start displaying some of these todos!
Part 3 — You’re here now.