Sanjay Purswani

@sanjsanj

A guide to TDD a React/Redux TodoList App — Part 3

Part 1 — Link.

Part 2 — Link.

Part 3 — You’re here now.

Part 4 — Link.

Redux

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.

The submitTodo action

We’ll use a constants file to ensure consistency, eliminate silly syntax errors and help document our code:

src/constants/index.js
src/actions/test.js

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:

src/actions/index.js

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.

The submitTodo reducer

This is where we store and return new states based on actions. Firstly we could test that it returns an initialState upon instantiation:

src/reducers/test.js

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:

src/reducers/index.js

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:

src/reducers/test.js

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:

src/reducers/index.js

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.

Wiring up Redux

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:

src/store.js

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:

src/index.js

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:

src/App.js

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:

src/App.test.js

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

Recap

We’ve learned how to:

  • Test drive creation of an action.
  • Test drive creation of a reducer.
  • Wire up Redux to our App so it can dispatch events and subscribe to state.

Up next

Let’s start making the most of being able to subscribe to state and actually start displaying some of these todos!

Part 1 — Link.

Part 2 — Link.

Part 3 — You’re here now.

Part 4 — Link.

More by Sanjay Purswani

Topics of interest

More Related Stories