Learning Redux with Puppies! Pt 2. Setup and adding dogs to our pen

In our previous post, we explained some core concepts of Flux and Redux. We also thought of the basic features of a dog pen. In this post, we’ll setup a project structure for our dog pen and start implementing our features.

All the code seen in this post is available on github.

Setting up a good development environment for our dogs

First of all, create a directory and call it “redux-dogpen”. Let’s initialize Git and npm. Then install Redux using Yarn.

npm init
git init
yarn add Redux

Structure is important to clarify different responsibilities of the application. I made my project structure like this.

redux-dogpen/
├── src/
│ ├── actioncreators/
│ ├── actiontypes/
│ ├── reducers/
│ └── app.js
├── package.json
├── yarn.lock

Coding the dogs

Action types is where you define the actions for the application. In my opinion you should be able to look at the action types and immediately be aware of what this application is about. Action types are defined as constants and are used throughout the reducers and action creators.

//actionTypes.js
export const ADD_DOG_TO_PEN = "ADD_DOG_TO_PEN";
export const REMOVE_DOG_FROM_PEN = "REMOVE_DOG_FROM_PEN";
export const RENAME_DOG = "RENAME_DOG";

Create this file inside the action creators directory. Three simple actions for adding, removing and renaming dogs. To further reuse these action, we want to create a set of helpers. We call these action creators. The action creator is a layer that prepares the action for the reducer. Usually we don’t add logic here. All it does is to shape the data coming in to the reducer.

//actionCreator.js
import {
ADD_DOG_TO_PEN,
REMOVE_DOG_FROM_PEN,
RENAME_DOG
} from "../actiontypes/actionTypes"

export function addDog(dog = {}) {
return {
type: ADD_DOG_TO_PEN,
dog
};
};

export function removeDog(index = 0) {
return {
type: REMOVE_DOG_FROM_PEN,
index
};
};

export function renameDog(dog = {}) {
return {
type: RENAME_DOG,
dog
};
};

After we have shaped the action, we can prepare the reducer. The reducer is where the changes in the state happen. The reducer is business heavy and is where most of the behavior lies. In our reducer, we create logic that corresponds to the desired action types.

//dogpen.js
import {
ADD_DOG_TO_PEN,
REMOVE_DOG_FROM_PEN,
RENAME_DOG
} from "../actiontypes/actionTypes"

function dogPen(state = [], action) {
switch (action.type) {
case ADD_DOG_TO_PEN:
return [
...state,
{
name: action.dog.name,
breed: action.dog.breed,
sex: action.dog.sex,
age: action.dog.age
}
];
case REMOVE_DOG_FROM_PEN:
return state.filter((dog, index) => {
return index != action.index
});
case RENAME_DOG:
return state.map((dog, index) => {
if (index === action.dog.index) {
return { ...dog,
name: action.dog.name
};
}
return dog;
});
default:
return state;
};
}

export default dogPen

Wonderful! The reducer takes two arguments, the state and the action... Here we have our three behaviors.

  • ADD_DOG_TO_PEN applies an object/dog to the existing array using the spread operator (triple dot annotation).
  • REMOVE_DOG_FROM_PEN will filter out the dog at the given index.
  • RENAME_DOG finds a dog at the given index and renames that dog.
  • Return state on default because you always got to return that state!

The reducer is shaped into a switch case. Every time you dispatch an action into a store, it will run every against every reducer connected to that store. A reducer always has to return a state, regardless if the state got modified or not.

Let’s put everything together! We’re creating a store and connecting the reducer.

//app.js
import { createStore } from 'redux';
import dogpen from './reducers/dogpen';
import {
addDog,
removeDog,
renameDog
} from './actioncreators/actionCreators';
let store = createStore(dogpen);

Now we’re ready to add those dogs! This is where we utilize those action creators. Now we can dispatch and get the state of the pen!

store.dispatch(addDog({
name: "pixie",
breed: "boxer",
sex: "female",
age: 2
}));
store.dispatch(addDog({
name: "steve",
breed: "mountain dog",
sex: "male",
age: 5
}));
store.dispatch(addDog({
name: "george",
breed: "finnish lapphund",
sex: "male",
age: 1
}));
console.log("--- ALL DOGS IN THE PEN ---");
console.log(store.getState());
******** CONSOLE **********
--- ALL DOGS IN THE PEN ---
[
{ name: 'pixie', breed: 'boxer', sex: 'female', age: 2 },
{ name: 'steve', breed: 'mountain dog', sex: 'male', age: 5 },
{ name: 'george', breed: 'finnish lapphund', sex: 'male', age: 1 } ]

We can also rename Steve.

store.dispatch(renameDog({
name: "candy",
index: 1
}));
console.log("--- STEVE GETS RENAMED TO CANDY ---");
console.log(store.getState());
******** CONSOLE **********
--- STEVE GETS RENAMED TO CANDY ---
[
{ name: 'pixie', breed: 'boxer', sex: 'female', age: 2 },
{ name: 'candy', breed: 'mountain dog', sex: 'male', age: 5 },
{ name: 'george', breed: 'finnish lapphund', sex: 'male', age: 1 } ]

Who let the dogs out?! George is free!

store.dispatch(removeDog(2));
console.log("--- GEORGE GETS REMOVED FROM THE PEN ---");
console.log(store.getState());
******** CONSOLE **********
--- GEORGE GETS REMOVED FROM THE PEN ---
[
{ name: 'pixie', breed: 'boxer', sex: 'female', age: 2 },
{ name: 'candy', breed: 'mountain dog', sex: 'male', age: 5 }
]

Now we have a dog pen, but we can’t adopt away any dogs. We need some more features. Next time we will create a reducer for people visiting the pen and we’ll see how we can connect the two reducers together. Part III coming soon!

Like what you read? Give Karl Solgård a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.