Git Product home page Git Product logo

combine-reducers-web-103017's Introduction

Redux Combine Reducers

Objectives

  1. Write action creators and reducers to modify different pieces of application state
  2. Build Redux's combineReducers() function
  3. Use the combineReducers() function to delegate different pieces of state to each reducer

Introduction

So far we have been using a single reducer to return a new state when an action is dispatched. This works great for a small application where we only need our reducer to manage the state of one resource. However, as you will see, when working with multiple resources, placing all of this logic in one reducer function can quickly become unwieldy.

Enter combineReducers() to save the day! In this lab, we'll see how Redux's combineReducers() function lets us delegate different pieces of state to separate reducer functions.

We'll do this in the context of a book application that we'll use to keep track of programming books that we've read.

We want our app to do two things:

  1. Keep track of all the books we've read: title, author, description.
  2. Keep track of the authors who wrote these books.

Determine Application State Structure

Our app will need a state object that stores two types of information:

  1. All our books, in an array
  2. Our authors, also in an array

Each of these types of information--all our books, and the authors--should be represented on our store's state object. We want to think of our store's state structure as a database. We will represent this as a belongs to/has many relationship, in that a book belongs to an author and an author has many books. So this means each author would have its own id, and each book would have an authorId as a foreign key.

With that, we can set the application state as:

{
  books: // array of books,
  authors: //array of favorite books
}

So our state object will have two top-level keys, each pointing to an array. For now, let's write a single reducer to manage both of these resources.

export function bookApp(state = {
  authors: [], 
  books: []
}, action) {
  switch (action.type) {

    case "ADD_BOOK":
      return Object.assign(state, {
        books: state.books.concat(action.book)
      });

    case "REMOVE_BOOK":
      const idx = state.books.indexOf(action.id);
      return Object.assign(state, {
        books: [
          state.books.slice(0, idx),
          state.books.slice(idx + 1),
        ]
      });

    case "ADD_AUTHOR":
        return Object.assign(state, {
          authors: state.authors.concat(action.author)
        });

    case "REMOVE_AUTHOR":
      const idx = state.authors.indexOf(action.id);
      return Object.assign(state, {
        authors: [
          state.authors.slice(0, idx),
          state.authors.slice(idx + 1)
        ]
      });

    default:
      return state;
    }
};

export const store = createStore(bookApp)

As you can see, just working with two resources increases the size of our reducer to almost twenty lines of code. Moreover, by placing each resource in the same reducer, we are coupling these resources together, where we would prefer to maintain their separation.

Refactor by using combineReducers

The combineReducers() function allows us to write two separate reducers, then pass each reducer to the combineReducers() function to produce the reducer we wrote above. Then we pass that combined reducer to the store. Let's write some code, and then we'll walk through it below.

import { combineReducers } from 'redux';

const rootReducer = combineReducers({
  books: booksReducer, 
  authors: authorsReducer
});

export const store = createStore(rootReducer);

function booksReducer(state = [], action) {
  switch (action.type) {

     case "ADD_BOOK":
      return state.concat(action.book);

    case "REMOVE_BOOK":
      const idx = state.indexOf(action.id);
      return [ ...state.slice(0, idx), ...state.slice(idx + 1) ];

    default:
      return state;
  }
}

function authorsReducer(state = [], action) {
  switch (action.type) {

    case "ADD_AUTHOR":
      return state.concat(action.author);

    case "REMOVE_AUTHOR":
      const idx = state.indexOf(action.id);
      return [ ...state.slice(0, idx), ...state.slice(idx + 1) ];

    default:
      return state;
  }
}

There's a lot of code there, so let's unpack it a bit. At the very top you see the following line:

const rootReducer = combineReducers({
  books: booksReducer, 
  authors: authorsReducer
});

We're telling Redux to produce a reducer which will return a state that has a key of books with a value equal to the return value of the booksReducer() and a key of authors with a value equal to the return value of the authorsReducer(). Now if you look at the booksReducer() and the authorsReducer() you will see that each returns a default state of an empty array. So by passing our rootReducer to the createStore method, the application maintains its initial state of { books: [], authors: [] }.

Examining Our New Reducers

Now if you examine the authorsReducer(), notice that this reducer only concerns itself with its own slice of the state. This makes sense. Remember that ultimately the array that the authorsReducer() returns will be the value to the key of authors. Similarly the authorsReducer() only receives as it's state argument the value of state.authors, in other words the authors array.

So examining the authorsReducer(), we see that we no longer retrieve the list of authors with a call to state.authors, but can access the list of authors simply by calling state.

function authorsReducer(state = [], action) {
  switch (action.type) {

    case "ADD_AUTHOR":
      return state.concat(action.author);

    case "REMOVE_AUTHOR":
      const idx = state.indexOf(action.id);
      return [ ...state.slice(0, idx), ...state.slice(idx + 1) ];

    default:
      return state;
  }
}

Dispatching Actions

The combineReducer() function returns to us one large reducer looks like the following:

function reducer(state = {
  authors: [], 
  books: []
}, action) {
  switch (action.type) {

    case "ADD_AUTHOR"
      return state.concat(action.author)

    case 'REMOVE_AUTHOR'
    ....
  }
};

Because of this, it means that we can dispatch actions the same way we always did. store.dispatch({ type: 'ADD_AUTHOR', { title: 'huck finn' } }); will hit our switch statement in the reducer and add a new author.
One thing to note, is that if you want to have more than one reducer respond to the same action, you can. For example, let's say that when a user inputs information about a book, the user also inputs the author's name. The action dispatched may look like the following: store.dispatch({ action: 'ADD_BOOK', book: { title: 'huck finn', authorName: 'Mark Twain' } });. Our reducers may look like the following:

function booksReducer(state = [], action) {
  switch (action.type) {

    case "ADD_BOOK":
      return state.concat(action.book);

    case "REMOVE_BOOK":
      const idx = state.indexOf(action.id);
      return [ ...state.slice(0, idx), ...state.slice(idx + 1) ];

    default:
      return state;
  }
}

function authorsReducer(state = [], action) {
  switch (action.type) {
    
    case "ADD_AUTHOR":
      return state.concat(action.author);
    
    case "REMOVE_AUTHOR":
      const idx = state.indexOf(action.id)
      return [ ...state.slice(0, idx), ...state.slice(idx + 1) ];
    
    case "ADD_BOOK":
      let existingAuthor = state.filter(author => author.name === action.author.name)
      if(!existingAuthor) {
        return state.concat(action.author);
      } else {
        return state
      }
    
    default:
      return state;
    }
}

So you can see that both the booksReducer() and the authorsReducer() will be modifying the store's state when an action of type ADD_BOOK is dispatched. The booksReducer() will provide the standard behavior of adding the new book. The authorsReducer() will look to see if there is an existing author of that type in the store, and if not add a new author.

Note: The above code has a bug in that we would not be able to add a foreign key of the authorId on a book, because when the books reducer receives the action, the related author would not exist yet. Therefore, we would take a different approach, and likely dispatch two sequential actions of types 'ADD_AUTHOR' and then 'ADD_BOOK'. However, the above example is to show that two separate reducers can respond to the same dispatched action.

Resources

View Combine Reducers Codealong on Learn.co and start learning to code for free.

combine-reducers-web-103017's People

Contributors

lukeghenco avatar jeffkatzy avatar achasveachas avatar ldgarber avatar

Watchers

 avatar Rishikesh Tirumala avatar James Cloos avatar  avatar Victoria Thevenot avatar  avatar Joe Cardarelli avatar Sara Tibbetts avatar The Learn Team avatar Cernan Bernardo avatar Sophie DeBenedetto avatar  avatar Antoin avatar  avatar  avatar Nicole Kroese  avatar Lisa Jiang avatar  avatar

Forkers

sethbass14

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.