loading...

React-Redux Flow, Terminologies, and Example

bouhm profile image Brian Pak ・6 min read

These are my notes and concise summary of the Redux portion of Udemy lecture "Modern React with Redux" by Stephen Grider along with other studies from the Redux documentation.

My summary assumes you have knowledge of React and some basic knowledge of what Redux is and what it's used for.

Redux

redux-cycle-2
Screen-cap from "Modern React with Redux"

ACTION CREATOR:
A function that creates an action

ACTION:
An object that contains information about how we want to change some data within our central state

action: { 
  type: [describes what change we want to make to our data], 
  payload: [context of what change we want to make] 
}

DISPATCH:
A function that takes in an action, makes copies of the action, and sends them out to the reducers.

REDUCER:
A function that takes in an action and some existing data, changes the data according to the type and payload of the action, and then sends the updated data to the state.

STATE:
An object that serves as the central repository of all data from the reducers.

 

React-Redux

With React-Redux, we use some components and functions to tie React and Redux together: Store, Provider, and Connect.

STORE:
The Store contains the consolidated reducers and the state.

PROVIDER:
The Provider is a component that has a reference to the Store and provides the data from the Store to the component it wraps.

CONNECT:
Connect is a function communicates with the Provider. Whatever component we wrap with Connect, that component will be able to get changes in the Store state from the Provider.

We can configure Connect to get just the part of the data we want from the Provider. Connect passes this down as props into the wrapped components.

The flow with react-redux looks like this:

  1. Create the Provider
  2. Pass it a reference to our Redux Store
  3. Wrap any component that needs to interact with the Store with Connect
  4. Connect passes down the different pieces of state and any action creators we need as props down to the wrapped component.

 

Booklist App Example

Now that we've established a high-level overview of the moving parts in our app, let's put it into practice.

Using our favorite example: A Booklist App.

Our app's Store state will look like this:

{
  books: [ ... ], // An array of book objects
  selectedBook: { title, author, ... } // Selected book object to display
}

Let’s say our Booklist App has the following functionalities:

  • Select & Display Book Information
  • Add New Book
  • Remove Book

Selecting a book should set selectedBook in the state to that book.

Adding/Removing a book should set books in the state to a new books array with the respective book added or removed.

We know each of these will be an action creator. Let's map out the actions they'll create.

// actions/booksActions.js

// Takes in the book being added
export const addBook = book => {
  return {
    type: "ADD_BOOK",
    payload: { 
      book: book
    }
  };
};


// Takes in the book to be removed
export const removeBook = book => {
  return {
    type: "REMOVE_BOOK",
    payload: { 
      book: book
    }
  };
};

// Books to show initially from fetch
export const setBooks = books => {
  return {
    type: "SET_BOOKS",
    payload: { 
      books: books 
    }
  };
};

// actions/selectedBookActions.js

// Takes in the book being selected
export const selectBook = selectedBook => {
  // Return an action
  return {
    type: "SELECT_BOOK",
    payload: { 
      selectedBook: selectedBook 
    }
  };
};

// P.S. I'm choosing to strictly follow conventions 
// and avoid using shorthand forms for clarity

Since our reducers will be responsible for different pieces of the state, we may construct our reducers like so:

// reducers/booksReducer.js

const booksReducer = (books = [], action) => {
  switch (action.type) {
    case "SET_BOOKS":
      return action.payload.books;
    case "ADD_BOOK":
      return [...books, action.payload.book];
    case "REMOVE_BOOK":
      let newBooks = [...books];
      newBooks.splice(books.indexOf(action.payload.book), 1);
      return newBooks;
    default:
      return books;
  }
};

// reducers/selectedBookReducer.js

const selectedBookReducer = (selectedBook = null, action) => {
  switch (action.type) {
    case "SELECT_BOOK":
      return action.payload.selectedBook;
    default:
      return selectedBook;
  }
};

They don't HAVE to be completely separated like this, but that's how we'll roll to keep things clear and simple. It's easy to see exactly what pieces of the state will be updated.

Now if we refer back to our react-redux flow, we have to create the Provider and pass it a reference to our Redux store. We'll do that in index.js.

// reducers/combinedReducers.js

import { combineReducers } from "redux";
import booksReducer from "./booksReducer";
import selectedBookReducer from "./selectedBookReducer";

// Consolidate our reducers into a combinedReducer
export default combineReducers({
  books: booksReducer,
  selectedBook: selectedBookReducer
});
// index.js

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import App from "./components/App";
import reducers from "./reducers/combinedReducers";

const rootElement = document.getElementById("root");

ReactDOM.render(
  <Provider store={createStore(reducers)}>
    <App />
  </Provider>,
  rootElement
);

Now that we have the Provider and store set up, we have to hook them up to the components that will interact with the store. Refer to the code in the CodeSandbox.

We have three main components, BookList, BookInfo, and NewBookForm. Our App.js will display the BookList for a list of books to select from, and BookInfo will display the book information for the selected book. NewBookForm will be a controlled form used to create a new book to add to the list of books.

Starting with BookList, we have to get our books in the state to display. We made the setBooks action creator for that, which creates an action with the payload containing the array of book objects we get from fetch.

We have to get this piece of the state from connect. We want to configure our connect such that we get the books array from the state passed down as props. We also need the action creators from booksActions.js to set books from fetched data and to make changes to books when adding/removing books.

Configuring connect

Connect takes in several arguments, but we will only be concerned with the first two: mapStateToProps and mapDispatchToProps.

Extracting data with mapStateToProps
mapStateToProps is essentially extracting just the pieces of the state you want into your component as props. It is important to note that when you specify a function for this argument, you are subscribing the component to changes in that part of the state. If the component does not need to re-render based on changes in the state, you can pass in null or undefined as the argument instead.

Dispatching actions with mapDispatchToProps
mapDispatchToProps can be defined as an object of action creators (there's Redux magic behind the scenes but this is a nice shorthand) and binds the dispatch of the store to each of the action creators. This allows you to use the actions creators through props in the component as you would by passing down functional props from the parent without Redux.

We can configure our connect for BookList like this:

// BookList.js

...

const mapStateToProps = state => {
  return {
    books: state.books
  };
};

export default connect(
  mapStateToProps,
  { setBooks, selectBook, removeBook }
)(BookList);

Since we just want books from state, our mapStateToProps just returns an object mapping the books from state to books which we'll be able to access with props.books. With this we are also subscribing our component to changes in books so that it will re-render with changes.

For mapDispatchToProps, we have the three action creators we are concerned with passed inside an object. Then we can call these functions with props.setBooks, props.selectBook, and props.removeBook respectively.

// BookList.js

...

  // I'm practicing using React hooks...
  // componentDidMount
  useEffect(() => {
    fetch(URL)    
    .then(res => res.json()    
    .then(books => props.setBooks(books))   
  }, []);

  const handleRemoveBook = (e, book) => {
    e.stopPropagation();
    props.removeBook(book);
  };

  return (
    <div className="book-list">
      {props.books.map((book, i) => (
        <div
          key={i}
          onClick={() => props.selectBook(book)}
          className="book-list-item"
          book={book}
        >
          <span className="title">{book.title}</span>
          <button onClick={e => handleRemoveBook(e, book)}>
            X
          </button>
      </div>
    )}
  </div>
)

...

Let's handle our addBook function in our NewBookForm, since we will be calling the function when we submit our form. Since we need to use the action creator addBook, we have to use connect.

// NewBookForm.js

...

 const handleSubmit = e => {
    e.preventDefault();
    props.addBook(book);
 };

...

export default connect(
  undefined,
  { addBook }
)(NewBookForm);

We are passing in undefined for mapStateToProps argument because our form does not care about changes in the state and we don't want to trigger unnecessary re-renders. We just specify that we want to use addBook in our mapDispatchToProps argument.

...and that's the bulk of the transition to using react-redux for this simple app. Once we properly configure our connect functions, we use the props just as we were doing before Redux. Only this time, we don't have to keep passing them down through the components.


References

Discussion

pic
Editor guide
Collapse
mehrdadarman profile image
MehrdadArman

Awesome

Collapse
roundand profile image
Francis Norton

This really helped me understand how the various redux components in a code base work together - thanks!

Collapse
isalevine profile image
Isa Levine

omg this is so good, thank you for sharing!!

Collapse
sanchocreativo profile image
sanchocreativo

This clear a lot of things for me, specially folder structure wise since I oftenly see mapDispatchToProps inside the component. Thanks!

Collapse
alokreddyk profile image
ReAl

Thanks a lot for sharing this amazing content!