/ javascript

Redux basic usage

In this example is basic usage of Redux (without React). In this example app's idea is a task list with task statuses.

Preparation

Good preparation before setting Redux is to plan what data and actions your application has. These are not required to set Redux, but they do reduce your coding work and refactoring.

Install packages

Install redux trough npm or yarn.

You might also need react-redux package if you're going to use Redux and React together.

Create initial state

State is a JSON object. Sometimes empty JSON object is enough, but couple properties help to keep on right track when coding. You can save your state object into it's own variable, but not always needed.

const InitialState = {
  tasks: []
}

Actions

Plain action

Actions are JavaScript objects which are passed trough reducers to make a new state for store. Actions are sent with store.dispatch(), but that's too much information for now.

Only thing really needed in action object is type property. It's a key property to identify your action type in when going trough the rabbit hole of reducers. Standard is that value is a string with only capitals and underscores.

{ type: 'ADD_TASK'}

And because data usually has some input from user, we add them to object too!

{
  type: 'ADD_TASK',
  title: 'Save the world!',
  status: 'Pending'
}

In bigger applications types can be their own constants in own file(s). This is also good place to put other content like status of tasks.

const ADD_TASK = 'ADD_TASK';

const TASK_STATUS = {
  DONE: 'Done',
  PENDING: 'Pending',
  NOT_STARTED: 'Not started',
  FAIL: 'Fail'
};

Action creator

Action creators are... *drumming*... functions which return action object! Helpful stuff if you have actions more complex than one property.

function addTask(title) {
  return {
    type: 'ADD_TASK',
    title: title,
    status: 'Not started'
  }
}

Useful to reduce mistakes and easier to type when dispatching actions. Though now you need to somehow document what action creators you have. But it's still easier to document than action objects!

Create reducers

Reducers are functions which include switch statements. There is gonna be one hook which activates by action's type property. This hook will return new state object for store. Store is place where state is saved, if you didn't know yet.

Difficult part comes here because reducers are not allowed to:

  • Mutate it's arguments
  • Perform function calls outside of it
  • Call non-pure functions

We give two parameters, stateand action. Parameter action is action object. You get from here action's ´type´ property to identify what to do. Also you get data needed to create new state. Parameter ´state´ is current state, which is returned if action type is not found. Also it's used to create new state. If you modify the state parameter, you're an extremely bad person.

function taskReducer (state, action) {
  switch (action.type) {
    case 'ADD_TASK':
      return Object.assign({}, state, {
        tasks: [
          ..state.tasks,
          {
            title: action.title,
            status: action.status
          }
        ]
      })
    default:
      return state
  }
}

As seen in example, a new state is created and returned. New state is created with Object.assign() or object spread operator {...state}.

Set initial state

There is two ways to set initial state with first reducer. Create if hook which returns initial state variable when state is undefined. This happens when first time run.

const InitialState = {
  tasks: []
}

function taskReducer (state, action) {
  if (typeof state === 'undefined') {
    return initialState
  }

  switch (action.type) {
    case 'ADD_TASK':
      return Object.assign({}, state, {
        tasks: [
          ..state.tasks,
          {
            title: action.title,
            status: action.status
          }
        ]
      })
    default:
      return state
  }
}

Or do it even faster! Use ES6 default arguments syntax.

const InitialState = {
  tasks: []
}

function taskReducer (state = initialState, action) {
  switch (action.type) {
    case 'ADD_TASK':
      return Object.assign({}, state, {
        tasks: [
          ..state.tasks,
          {
            title: action.title,
            status: action.status
          }
        ]
      })
    default:
      return state
  }
}

It is also possible to set initial state when creating a store. You decide.

The store

Store is the vault of states. standard is to use only one store in app. Store's responsibilities are:

  • Give a state.
  • Take action, put it trough reducers and and create a new state.
  • Register listeners.
  • Handle listeners.

Store can be created after there is at least one reducer. Or you can pass a function without reducer which is stupid thing to do.

import { createStore } from 'redux'

let store = createStore(taskReducer)

Now we have store and we can start creating new states. This is possible because now have initial state, tasks and reducers working!

If you just skipped whole article, all my hard work has been empty. *sad puppy*

Get current state

Use store.getState() to return the current state.

Dispatch actions

Dispatching actions mean that we give an action for the store. Store drops action trough reducers and get's new state. Dispatching an action works with store.dispatch() method.

Before we dispatch any action, our state looks like { tasks: [] }.

store.dispatch({
  type: 'ADD_ACTION',
  title: 'Save puppies!',
  status: 'Not started'
});

So we gave an action object itself, but we can also use that action maker we made!

store.dispatch(addTask('Save puppies!'));

And the new state will look same after both examples:

{
  tasks: [
    {
      title: 'Save puppies!',
      status: 'Not started'
    }
  ]
}

That's it!

This was kind a minified example of what redux does and how it works. But this was the target of this post. Understanding the backbone of Redux. My idea is to open up more at least reducers in future.