Login - Part 5


In this video we are going to handle the last major step in the Login journey: Updating our application state.

This is where the term "Reducer" comes into play.

The reducer / reducing function is something we need to code.

We will start by defining a function which gets given state as it's first argument, and an action is its only other argument.

The convention I'm following is that whatever name this function has will serve as the key for this part of the next resulting application state:

// /src/reducers/authReducer.js

import * as types from '../constants/actionTypes';

export default function auth(state = {
  isAuthenticated: false,
  id: undefined,
  username: undefined
}, action) {

  switch (action.type) {

    case types.LOGIN__COMPLETED: {
      const { id, username } = action.payload;
      return Object.assign({}, state, {
        isAuthenticated: true,
        id,
        username
      })
    }

    case types.LOGOUT__COMPLETED: {
      return Object.assign({}, state, {
        isAuthenticated: false,
        id: undefined,
        username: undefined
      })
    }
  }
}

In the video we work through the understanding of how the function comes into being.

A really nice JavaScript ES6 method used in this code is Object.assign. By taking an empty object {} as our target, we can pass in one or more objects to the final object. Seeing code makes this easier to see:

    const o1 = { a: 1, b: 1, c: 1 };
    const o2 = { b: 2, c: 2 };
    const o3 = { c: 3 };

    let outcome = Object.assign(o1, o2, o3);

    assert.deepEqual(outcome, { a: 1, b: 2, c: 3 }); // true

And this is fine, but be careful here as in this instance, o1 would have been changed.

    let outcome = Object.assign(o1, o2, o3);

    assert.deepEqual(outcome, { a: 1, b: 2, c: 3 });

    console.log(o1); // Object {a: 1, b: 2, c: 3}

So, to stop this, the first parameter is set to be an empty object:

    let outcome = Object.assign({}, o1, o2, o3);

    assert.deepEqual(outcome, { a: 1, b: 2, c: 3 });

    console.log(o1); // Object {a: 1, b: 1, c: 1}

In our case, that's going to give us the next state being whatever the current state is, but with only the subset of changes (action.payload) we want applying.

We also add in the isAuthenticated boolean value, giving ourselves a handy shortcut to checking if the user is or is not logged in. You could do this differently by using Redux selectors (e.g. reselect), but I don't need anything that fancy at this stage.

Also, I've added in the inverse for LOGOUT_COMPLETED - unsetting stuff, and generally cleaning up. We don't immediately need that, but doing it now will save us a job in the future.

You must remember to add your new reducer into the list of reducers that Redux will be aware of:

// /src/reducers/index.js

import {combineReducers} from 'redux';
import {routerReducer} from 'react-router-redux';
import {reducer as formReducer} from 'redux-form';
import auth from './authReducer';

const rootReducer = combineReducers({
  form: formReducer,
  routing: routerReducer,
  auth,
});

export default rootReducer;

And with that we can define a mapStateToProps function, which when passed into Redux connect() allows us to augment our component's props with interesting parts of our application's state.

function mapStateToProps(state) {
  return {
    auth: state.auth
  }
}

export default connect(
  mapStateToProps
)(App);

And then inside our component's render method, we could use this prop:

render() {
    return (
        {this.props.auth.isAuthenticated ? 'Welcome back!' : 'You must login'}
    )
}

We will do something like this shortly, when adding a Bootstrap Navbar to our UI.

Code For This Course

Get the code for this course.

Code For This Video

Get the code for this video.

Episodes