Thinkster
React

JWT Authentication with React & Redux

Build React components and Redux reducers/middleware to enable authentication via JWT

Valeri Karpov Up to date (Jan '17) React

This tutorial is a part of the "Build a Real World App with React & Redux" tutorial series.

It's time to actually hook up the login view. But first, in order to make this easier, let's do some refactoring to break up the reducers into smaller independent chunks.

Refactoring to use combineReducers()

You can write one big monolithic reducer, but that makes tasks like cleaning up state when the view changes confusing. Redux's combineReducers() function lets you build up a reducer out of independent reducers. First, let's write 3 separate reducers.

Take the global feed reducer logic from the home page and stuff it into reducers/home.js:
export default (state = {}, action) => {
  switch (action.type) {
    case 'HOME_PAGE_LOADED':
      return {
        ...state,
        articles: action.payload.articles
      };
  }

  return state;
};

Next, let's create a 'common' reducer that'll take care of the appName property:

Create the 'common' reducer
const defaultState = {
  appName: 'Conduit'
};

export default (state = defaultState, action) => {
  return state;
};

Finally, let's create a reducers/auth.js file that will contain all the auth-specific logic. For now, it'll do nothing.

Stub out reducers/auth.js for auth logic
export default (state = {}, action) => {
  return state;
};

In store.js, let's import in these reducers and build them into a single reducer using combineReducers().

Use combineReducers to import these three reducers
import { applyMiddleware, createStore, combineReducers } from 'redux';
import { promiseMiddleware } from './middleware';
import auth from './reducers/auth';
import common from './reducers/common';
import home from './reducers/home';

const reducer = combineReducers({
  auth,
  common,
  home
});

const middleware = applyMiddleware(promiseMiddleware);

const store = createStore(reducer, middleware);

export default store;

With these changes, we're going to need to change the mapStateToProps function for all components that have them. For example, in App.js, remember that appName is now in the common reducer? To account for that, mapDispatchToProps() needs to get appName from the state's common property.

Update the App component
import Header from './Header';
import Home from './Home';
import React from 'react';
import { connect } from 'react-redux';

const mapStateToProps = state => ({
  appName: state.common.appName
});

class App extends React.Component {
  // ...
}

App.contextTypes = {
  router: React.PropTypes.object.isRequired
};

export default connect(mapStateToProps, () => ({}))(App);

Now we need to apply the same changes to the Home component:

Update the Home component
import Banner from './Banner';
import MainView from './MainView';
import React from 'react';
import agent from '../../agent';
import { connect } from 'react-redux';

const Promise = global.Promise;

const mapStateToProps = state => ({
  appName: state.common.appName
});

const mapDispatchToProps = dispatch => ({
  // ...
});

class Home extends React.Component {
  // ...
}

export default connect(mapStateToProps, mapDispatchToProps)(Home);

And the MainView component:

Update the MainView component
import ArticleList from '../ArticleList';
import React from 'react';
import { connect } from 'react-redux';

const mapStateToProps = state => ({
  articles: state.home.articles
});

const MainView = props => {
  // ...
};

export default connect(mapStateToProps, () => ({}))(MainView);