WEEK-15 DAY-3
React + Redux




Redux Learning Objectives: Part 3

Just like Luke Skywalker returning to Dagobah to complete his Jedi training with Yoda, it’s time for you to return to learning Redux one more time. In this final section on Redux, you’ll learn how to use the React-Redux library to connect components to a Redux store and how to use middleware and thunks to interact with an API.

After completing this final section on Redux, you should be able to:


Higher-Order Components

In React, higher-order components (HOCs) are a pattern for reusing component logic. Ultimately, higher order components allow you to dynamically generate wrapper components.

Before React existed, developers leveraged JavaScript’s support for functions as first-class objects, meaning that functions can be treated just like any other data type (i.e. stored in a variable, object, or array, passed as an argument to a function, or returned from a function). Understanding what higher-order functions are and how they work will help you to understand and use higher-order components in React.

When you finish this article, you should be able to:

Reviewing higher-order functions

Functions that operate on other functions, either by receiving them as arguments or returning them, are called higher-order functions. Said differently, higher-order functions are functions that:

Using closures with higher-order functions

A closure, also known as lexical scoping, is a function that uses free variables, variables defined outside of its scope. Closures come in handy when writing higher-order functions. Consider the following code:

const calculator = function (operationCb) { // high-order function
  return function (op1, op2) { // closure
    console.log(`calling with ${op1} ${op2}`);
    const result = operationCb(op1, op2);
    console.log(`equals ${result}`);
  };
}

const addition = function (n1, n2) { // callback
  console.log(`${n1} + ${n2}`);
  return n1 + n2;
}

const adder = calculator(addition);
adder(1, 2);
// calling with 1 2
// 1 + 2
// equals 3

The calculator function receives a callback as an argument (operationCb) which is called in the anonymous function calculator returns. This return value would not work if the inner function could not close over operationCb, a variable defined outside of its scope.

Defining higher-order functions with arrow functions

Arrow functions make it easy to write higher-order functions. The two examples below illustrate the same function:

// Without arrow functions (ES5):
function foo(arg1) {
  return function(arg2) {
    return function(arg3) {
      console.log(`${arg1} came before ${arg2} and ${arg3} came last`);
    };
  };
}

// With arrow functions (ES6):
const foo = arg1 => arg2 => arg3 => {
  console.log(`${arg1} came before ${arg2} and ${arg3} came last`);
};

Here’s the earlier calculator function rewritten using arrow functions:

const calculator = (operationCb) => (op1, op2) => {
  console.log(`calling with ${op1} ${op2}`);
  const result = operationCb(op1, op2);
  console.log(`equals ${result}`);
};

Note: Remember, ES6 arrow functions, unlike normal JavaScript functions, are automatically bound to the context (this) that existed when they were defined. In other words, this means the same thing inside an arrow function that it does outside of it.

Leveraging higher-order components (HOCs)

In the same way that higher-order functions can receive a function as an argument and return a new function, higher-order components or HOCs receive a React component as an argument and return a new component.

ProtectedRoute and AuthRoute

In the React Twitter Lite project, you created two HOCs to control what pages users could see based upon their authentication status. The ProtectedRoute HOC ensured that only logged users could view the Profile and Home pages while the AuthRoute HOC prevented logged in users from viewing the Login or Registration pages:

export const ProtectedRoute = ({ component: Component, path, currentUserId, exact }) => {
  return (
    <Route
      path={path}
      exact={exact}
      render={(props) =>
        currentUserId ? <Component {...props} /> : <Redirect to="/login" />
      }
    />
  );
};
export const AuthRoute = ({ component: Component, path, currentUserId, exact }) => {
  return (
    <Route
      path={path}
      exact={exact}
      render={(props) =>
        currentUserId ? <Redirect to="/" /> : <Component {...props} />
      }
    />
  );
};

Notice how both components accept a component via the component parameter and return a Route component (that renders the passed component via a render prop).

Using HOCs to keep code DRY

HOCs give React developers a powerful way to reuse component logic so that they can keep their code DRY.

When learning about Redux container components, you might have noticed that the FruitManagerContainer and FarmerManagerContainer components in the Fruit Stand application contain the same componentDidMount and componentWillUnmount lifecycle method implementations:

// ./src/components/FruitManagerContainer.js

import React from 'react';
import store from '../store';
import {
  addFruit,
  addFruits,
  sellFruit,
  sellOut,
} from '../actions/fruitActions';
import FruitManager from './FruitManager';

class FruitManagerContainer extends React.Component {
  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }

  componentWillUnmount() {
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }

  add = (fruit) => {
    store.dispatch(addFruit(fruit));
  }

  addBulk = (fruit) => {
    store.dispatch(addFruits(fruit));
  }

  sell = (fruit) => {
    store.dispatch(sellFruit(fruit));
  }

  sellAll = () => {
    store.dispatch(sellOut());
  }

  render() {
    const { fruit } = store.getState();
    const distinctFruit = Array.from(new Set(fruit)).sort();

    return (
      <FruitManager
        fruit={fruit}
        distinctFruit={distinctFruit}
        add={this.add}
        addBulk={this.addBulk}
        sell={this.sell}
        sellAll={this.sellAll} />
    );
  }
}

export default FruitManagerContainer;
// ./src/components/FarmerManagerContainer.js

import React from 'react';
import store from '../store';
import { hireFarmer, payFarmer } from '../actions/farmersActions';
import FarmerManager from './FarmerManager';

class FarmerManagerContainer extends React.Component {
  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }

  componentWillUnmount() {
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }

  pay = (id) => {
    store.dispatch(payFarmer(id));
  }

  hire = (name) => {
    store.dispatch(hireFarmer(name));
  }

  render() {
    const { farmers: farmersState } = store.getState();
    const farmers = Object.values(farmersState);

    return (
      <FarmerManager
        farmers={farmers}
        pay={this.pay}
        hire={this.hire} />
    );
  }
}

export default FarmerManagerContainer;

While the amount of duplicated code is currently relatively small, keep in mind that the approach of using the forceUpdate method to render the component whenever state is updated in the store isn’t optimal. Calling forceUpdate causes render to be called without first calling shouldComponentUpdate.

Ideally, each container component should contain logic to determine if the state that it retrieves from the store has actually changed before continuing with rendering. Adding the code for this logic would increase the amount of code duplicated in each container component.

React-Redux connect

React-Redux, a library from the creators of Redux, includes a higher-order component named connect that you can use to create container components. Using connect frees you from having to manually create the FruitManagerContainer and FarmerManagerContainer container components. Using connect also eliminates boilerplate code so you can focus on what makes each container unique: selecting specific slices of state from the store and writing functions to dispatch specific actions. Even better, connect includes the logic that’s needed to optimize the rendering performance, only rendering when the state retrieved from the store has actually changed.

Over the next couple of articles, you’ll learn how to use connect and the React-Redux library.

Writing a HOC for creating container components

Higher-order components like React-Redux connect, while extremely useful, can be confusing to understand. To help give you some insight into the underlying implementation of the connect function, you can build a your own custom version of connect (albeit without the logic that’s needed to optimize rendering performance).

Don’t worry if you struggle to fully understand the following custom version of connect. It takes time and practice to get comfortable with the techniques that are used to create higher-order components. And remember, you don’t have to build your connect function! Going forward, you’ll use the well-maintained, highly optimized connect function provided by the React-Redux library.

Consider the following custom connect function:

// ./src/connect.js

import React from 'react';
import store from './store';

const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (
  class extends React.Component {
    render() {
      let stateProps = {};
      if (mapStateToProps) {
        stateProps = mapStateToProps(store.getState(), this.props);
      }

      let dispatchProps = {};
      if (mapDispatchToProps) {
        dispatchProps = mapDispatchToProps(store.dispatch, this.props);
      }

      const propsToSpread = Object.assign({}, this.props, stateProps, dispatchProps);

      return (
        <WrappedComponent {...propsToSpread} />
      );
    }

    componentDidMount() {
      this.unsubscribe = store.subscribe(() => {
        this.forceUpdate();
      });
    }
  
    componentWillUnmount() {
      if (this.unsubscribe) {
        this.unsubscribe();
      }
    }
  }
);

export default connect;

The connect function is a higher-order component that returns a function. That function returns an anonymous class component that wraps the component passed in via the WrappedComponent parameter.

The anonymous class component is a “generic” container component that connects WrappedComponent to the Redux store by:

Comparing nested arrow functions to regular functions

As you saw earlier in this article, higher-order functions can be written using arrow functions or regular functions. The connect higher-order function can be written using arrow functions and implicit return statements:

const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (
  class extends React.Component {
    // Code removed for brevity.
  }
);

Or can be rewritten using regular functions and explicit return statements:

function connect(mapStateToProps, mapDispatchToProps) {
  return function(WrappedComponent) {
    return class extends React.Component {
      // Code removed for brevity.
    };
  };
};

The arrow function syntax, while concise, can be confusing to read.

Dynamically setting component attributes

The connect function’s mapStateToProps and mapDispatchToProps parameters are both functions that are called within the anonymous class’ render method:

const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (
  class extends React.Component {
    render() {
      let stateProps = {};
      if (mapStateToProps) {
        stateProps = mapStateToProps(store.getState(), this.props);
      }

      let dispatchProps = {};
      if (mapDispatchToProps) {
        dispatchProps = mapDispatchToProps(store.dispatch, this.props);
      }

      // Code removed for brevity.
    }

    // Code removed for brevity.
  }
);

The Redux store’s current state, retrieved using store.getState, is passed to the mapStateToProps function and the store’s dispatch method is passed to the mapDispatchToProps function. The mapStateToProps and mapDispatchToProps functions also receive the anonymous class component’s props (via this.props).

But what are the mapStateToProps and mapDispatchToProps functions exactly? To answer that question, take a look at an example of using the connect function to define the FruitManagerContainer component:

// ./src/components/FruitManagerContainer.js

import connect from '../connect';
import {
  addFruit,
  addFruits,
  sellFruit,
  sellOut,
} from '../actions/fruitActions';
import FruitManager from './FruitManager';

const mapStateToProps = (state) => ({
  fruit: state.fruit,
  distinctFruit: Array.from(new Set(state.fruit)).sort(),
});

const mapDispatchToProps = (dispatch) => ({
  add: (fruit) => dispatch(addFruit(fruit)),
  addBulk: (fruit) => dispatch(addFruits(fruit)),
  sell: (fruit) => dispatch(sellFruit(fruit)),
  sellAll: () => dispatch(sellOut()),
});

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

mapStateToProps is a function that accepts store’s current state and returns an object that maps state data to one or more properties. mapDispatchToProps is also a function, but it accepts the store’s dispatch method and returns an object whose properties are set to functions that can be called dispatch actions to the store.

In the connect function, Object.assign is then used to combine the stateProps and dispatchProps objects with this.props (the anonymous class component’s props):

const propsToSpread = Object.assign({}, this.props, stateProps, dispatchProps);

Then the properties defined on the propsToSpread object become props on WrappedComponent using JSX spread attribute syntax (...):

return (
  <WrappedComponent {...propsToSpread} />
);

Within the FruitManager component (the component that the connect function’s WrappedComponent is wrapping), parameter destructuring is used to reference the props provided by the mapStateToProps and mapDispatchToProps functions. These props are then passed as props to the appropriate child presentational component:

// ./src/components/FruitManager.js

import React from 'react';
import FruitList from './FruitList';
import FruitSeller from './FruitSeller';
import FruitQuickAdd from './FruitQuickAdd';
import FruitBulkAdd from './FruitBulkAdd';

const FruitManager = ({ fruit, distinctFruit, add, addBulk, sell, sellAll }) => {
  return (
    <div>
      <h2>Available Fruit</h2>
      <FruitList fruit={fruit} />
      <h2>Fruit Inventory Manager</h2>
      <FruitSeller distinctFruit={distinctFruit} sell={sell} sellAll={sellAll} />
      <FruitQuickAdd add={add} />
      <FruitBulkAdd addBulk={addBulk} />
    </div>
  );
};

export default FruitManager;

Visualizing the flow of data all the from the mapStateToProps and mapDispatchToProps function definitions within the FruitManagerContainer.js file through the connect higher-order component down to the FruitManager wrapped component is difficult to do. Understanding how to use React’s ability to dynamically set component attributes using JSX spread attribute syntax is one of the more challenging aspects of writing higher-order components.

Just remember that you don’t need to fully understand the above example to use the connect function provided by the React-Redux library.

Reviewing a completed Fruit Stand example

To review and run a completed Fruit Stand example application that utilizes the above custom connect higher-order component to create container components, clone the redux-fruit-stand-examples repo.

After cloning the repo, open a terminal and browse to the fruit-stand-redux-with-react-generic-container folder. Run the command npm install to install the project’s dependencies. Then use the command npm start to run the Fruit Stand application.

This Fruit Stand example application is a React application created by the Create React App tooling. When running the application using npm start, the application should automatically open in your default browser. If it doesn’t, you can manually browse to http://localhost:3000/ to view the application.

What you learned

In this article, you learned about higher-order components (HOCs) including how to write a higher-order component that accepts a component as an argument and returns a new component.


React-Redux: <Provider/>

As you learned in earlier articles, the integration techniques that you were initially shown were just a starting point with using Redux with React. Now that you’ve learned the basics of how React components interact with a Redux store, it’s time to learn how you can use React-Redux, a library from the creators of Redux, to improve upon those techniques.

To prepare to use the connect function from the React-Redux library, you need to first add a <Provider /> component to your React application. When you finish this article, you should be able to use the <Provider /> component to make your Redux store available to any nested components that have been wrapped in the connect function.

Understanding the advantages of the <Provider /> component

Oftentimes, a deeply nested component will need access to the store, while its parents do not. Using vanilla React, these parents would have to receive the store prop in order to pass it down to its child.

Consider the example below:

// App.js

import React from 'react';

const UserInfo = ({ store }) => (
  <div>
    {store.getState().username}
  </div>
);

const Header = ({ store }) => (
  <div>
    <UserInfo store={store} />
  </div>
);

const App = ({ store }) => (
  <div>
    <Header store={store} />
  </div>
);

export default App;
// index.js

import React from 'react';
import ReactDOM from 'react-dom';

import { createStore } from 'redux';
import reducer from './reducer';
import App from './App';

const store = createStore(reducer);

ReactDOM.render(
  <React.StrictMode>
    <App store={store} />
  </React.StrictMode>,
  document.getElementById('root')
);

The store is created in the index.js file, but the UserInfo component that needs to access it is deeply nested. Thus, the store must be passed as a prop down the entire component tree, even though components such as the Header do not need to access the store.

This pattern, called prop threading, is tedious and error-prone. You can avoid it by using the <Provider />/connect API provided by React-Redux.

Preparing your React application for server-side rendering

Using <Provider /> also helps to prepare your React/Redux application to utilize server-side rendering. Server-side rendering allows you to render components to static markup, which can help to reduce the initial loading time of your application.

React server-side rendering is an advanced topic that won’t be covered in this course. For more information, see the official React and Redux docs.

Adding <Provider />

Before adding <Provider /> to your React application, use npm to install the react-redux package:

npm install react-redux

Then, in the entry point for your application (typically the index.js file), import the Provider component and your Redux store:

import { Provider } from 'react-redux';
import store from './store';

Then use the Provider component to wrap your App component and set its store prop to your Redux store:

<Provider store={store}>
  <App />
</Provider>

Here’s what your completed index.js file will look like:

// ./src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import store from './store';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

<Provider /> is simply a React component in which you wrap the rest of the application. The Provider component receives the store as a prop and sets a store context. Because you wrapped the entire App in the Provider component, all your components can access the store context.

Components that need to access the store context have to be wrapped in a container component created by the connect function, which converts the store context into a store prop. You’ll learn how to use the connect function in the next article.

Understanding how <Provider /> relates to the React Context API

The store context set by <Provider /> is the same React Context that you used in an earlier lesson to manage global state within a React application. You can see this in action by reviewing the react-redux source code on GitHub:

// https://github.com/reduxjs/react-redux/blob/master/src/components/Context.js

import React from 'react'

export const ReactReduxContext = /*#__PURE__*/ React.createContext(null)

if (process.env.NODE_ENV !== 'production') {
  ReactReduxContext.displayName = 'ReactRedux'
}

export default ReactReduxContext

And while it’s rarely used, it’s possible to import the context from React-Redux and use the <Consumer /> to access the store:

import { ReactReduxContext } from 'react-redux';

// in your connected component
render() {
  return (
    <ReactReduxContext.Consumer>
      {({ store }) => {
        // do something with the store here
      }}
    </ReactReduxContext.Consumer>
  );
}

You can also connect the Redux <Provider /> component to the <Context.Provider /> component that passes the value of a context object to all child components. Redux’s <Provider /> component simply passes the Redux store, instead of a context value.

What you learned

In this article, you learned how to use the <Provider /> component to make your Redux store available to any nested components that have been wrapped in the connect function.


React-Redux: connect()

The React-Redux library allows you to access the store context set by the <Provider /> in a powerful and convenient way via the connect function. Using connect, you can pass specific slices of the store’s state and specific action-dispatches to a React component as props. A component’s props then serve as its API to the store, making the component more modular and less burdened by Redux boilerplate.

When you finish this article, you should be able to use the connect function to give a component access to a Redux store.

Calling connect

The React-Redux connect function is a higher-order function. It takes two arguments (plus a couple optional arguments you can read more about in the docs) and returns a function:

const createConnectedComponent = connect(
  mapStateToProps,
  mapDispatchToProps
);

The returned function (createConnectedComponent) then takes the React component that needs access to the Redux store and returns a new React component:

const ConnectedComponent = createConnectedComponent(MyComponent);

export default ConnectedComponent;

ConnectedComponent will render MyComponent, passing along props as determined by the mapStateToProps and mapDispatchToProps arguments.

You can combine these function calls into a single statement by immediately calling the function returned by the connect method (similarly to how you immediately call a function expression when defining an IIFE):

const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponent);

export default ConnectedComponent;

Typically, to keep things as concise as possible, the ConnectedComponent variable is omitted:

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

Defining mapStateToProps(state, [ownProps])

This first argument to connect is a function, mapStateToProps. It tells connect how to map the state into your component’s props.

It must take as an argument the store’s state (supplied by the Provider’s store context) and return an object containing the relevant props for your component.

const MyComponent = ({ name }) => (
  <div>{name}</div>
);

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

const ConnectedComponent = connect(mapStateToProps)(MyComponent);

In the example above, ConnectedComponent will render MyComponent, passing name as a prop.

ownProps (optional)

A component with explicit props passed down from its parent (e.g. <ConnectedComponent lastName={'Wozniak'}/>) can merge those props with slices of state via ownProps, a optional second argument to mapStateToProps:

const mapStateToProps = (state, ownProps) => ({
  firstName: state.name,
  initials: `${state.name[0]}. ${ownProps.lastName[0]}.`
});

const ConnectedComponent = connect(mapStateToProps)(MyComponent);

You can also access React Router props, such match and history through ownProps. Imagine you have a users slice of state, and you want to pass a specific user’s name based on a :userId parameter. You can access the parameter from within the mapStateToProps function with ownProps.match.params.userId:

const mapStateToProps = (state, ownProps) => ({
  name: state.users[ownProps.match.params.userId].name,
});

const ConnectedComponent = connect(mapStateToProps)(MyComponent);

Defining mapDispatchToProps

mapDispatchToProps is the second argument to connect. It’s a function that accepts the store’s dispatch method and returns an object containing functions that can be called to dispatch actions to the store. These action dispatchers are then passed as props to the component.

const deleteTodo = (id) => ({ type: 'DELETE_TODO', id }); // action creators
const addTodo = (msg) => ({ type: 'ADD_TODO', msg });

const mapDispatchToProps = (dispatch) => ({
  handleDelete: (id) => dispatch(deleteTodo(id)),
  handleAdd: (msg) => dispatch(addTodo(msg))
});

const ConnectedComponent = connect(null, mapDispatchToProps)(MyComponent);

Notice that in the example above, the connect function is invoked with null as a placeholder for the mapStateToProps function. The connect function expects mapStateToProps as its first argument and mapDispatchToProps as its second argument.

Putting it all together

const MyComponent = ({ firstName, initials, handleAdd, handleDelete }) => {
  return <div>...</div>;
};

const mapStateToProps = (state, ownProps) => ({
  firstName: state.name,
  initials: `${state.name[0]}. ${ownProps.lastName[0]}.`
});

const mapDispatchToProps = (dispatch) => ({
  handleDelete: (id) => dispatch(deleteTodo(id)),
  handleAdd: (msg) => dispatch(addTodo(msg))
});

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

MyComponent will receive firstName, initials, handleDelete, and handleAdd as props.

And remember, unlike the earlier attempt at writing a custom connect higher-order component, the React-Redux library’s connect function contains logic to optimize the rendering of your connected components.

Reviewing a completed Fruit Stand example

To review and run a completed Fruit Stand example application that utilizes the React-Redux library, clone the redux-fruit-stand-examples repo.

After cloning the repo, open a terminal and browse to the fruit-stand-redux-with-react-official-bindings folder. Run the command npm install to install the project’s dependencies. Then use the command npm start to run the Fruit Stand application.

This Fruit Stand example application is a React application created by the Create React App tooling. When running the application using npm start, the application should automatically open in your default browser. If it doesn’t, you can manually browse to http://localhost:3000/ to view the application.

What you learned

In this article, you learned how to use the React-Redux library’s connect function to give a component access to a Redux store.


React-Redux: connect()

The React-Redux library allows you to access the store context set by the <Provider /> in a powerful and convenient way via the connect function. Using connect, you can pass specific slices of the store’s state and specific action-dispatches to a React component as props. A component’s props then serve as its API to the store, making the component more modular and less burdened by Redux boilerplate.

When you finish this article, you should be able to use the connect function to give a component access to a Redux store.

Calling connect

The React-Redux connect function is a higher-order function. It takes two arguments (plus a couple optional arguments you can read more about in the docs) and returns a function:

const createConnectedComponent = connect(
  mapStateToProps,
  mapDispatchToProps
);

The returned function (createConnectedComponent) then takes the React component that needs access to the Redux store and returns a new React component:

const ConnectedComponent = createConnectedComponent(MyComponent);

export default ConnectedComponent;

ConnectedComponent will render MyComponent, passing along props as determined by the mapStateToProps and mapDispatchToProps arguments.

You can combine these function calls into a single statement by immediately calling the function returned by the connect method (similarly to how you immediately call a function expression when defining an IIFE):

const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(MyComponent);

export default ConnectedComponent;

Typically, to keep things as concise as possible, the ConnectedComponent variable is omitted:

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

Defining mapStateToProps(state, [ownProps])

This first argument to connect is a function, mapStateToProps. It tells connect how to map the state into your component’s props.

It must take as an argument the store’s state (supplied by the Provider’s store context) and return an object containing the relevant props for your component.

const MyComponent = ({ name }) => (
  <div>{name}</div>
);

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

const ConnectedComponent = connect(mapStateToProps)(MyComponent);

In the example above, ConnectedComponent will render MyComponent, passing name as a prop.

ownProps (optional)

A component with explicit props passed down from its parent (e.g. <ConnectedComponent lastName={'Wozniak'}/>) can merge those props with slices of state via ownProps, a optional second argument to mapStateToProps:

const mapStateToProps = (state, ownProps) => ({
  firstName: state.name,
  initials: `${state.name[0]}. ${ownProps.lastName[0]}.`
});

const ConnectedComponent = connect(mapStateToProps)(MyComponent);

You can also access React Router props, such match and history through ownProps. Imagine you have a users slice of state, and you want to pass a specific user’s name based on a :userId parameter. You can access the parameter from within the mapStateToProps function with ownProps.match.params.userId:

const mapStateToProps = (state, ownProps) => ({
  name: state.users[ownProps.match.params.userId].name,
});

const ConnectedComponent = connect(mapStateToProps)(MyComponent);

Defining mapDispatchToProps

mapDispatchToProps is the second argument to connect. It’s a function that accepts the store’s dispatch method and returns an object containing functions that can be called to dispatch actions to the store. These action dispatchers are then passed as props to the component.

const deleteTodo = (id) => ({ type: 'DELETE_TODO', id }); // action creators
const addTodo = (msg) => ({ type: 'ADD_TODO', msg });

const mapDispatchToProps = (dispatch) => ({
  handleDelete: (id) => dispatch(deleteTodo(id)),
  handleAdd: (msg) => dispatch(addTodo(msg))
});

const ConnectedComponent = connect(null, mapDispatchToProps)(MyComponent);

Notice that in the example above, the connect function is invoked with null as a placeholder for the mapStateToProps function. The connect function expects mapStateToProps as its first argument and mapDispatchToProps as its second argument.

Putting it all together

const MyComponent = ({ firstName, initials, handleAdd, handleDelete }) => {
  return <div>...</div>;
};

const mapStateToProps = (state, ownProps) => ({
  firstName: state.name,
  initials: `${state.name[0]}. ${ownProps.lastName[0]}.`
});

const mapDispatchToProps = (dispatch) => ({
  handleDelete: (id) => dispatch(deleteTodo(id)),
  handleAdd: (msg) => dispatch(addTodo(msg))
});

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

MyComponent will receive firstName, initials, handleDelete, and handleAdd as props.

And remember, unlike the earlier attempt at writing a custom connect higher-order component, the React-Redux library’s connect function contains logic to optimize the rendering of your connected components.

Reviewing a completed Fruit Stand example

To review and run a completed Fruit Stand example application that utilizes the React-Redux library, clone the redux-fruit-stand-examples repo.

After cloning the repo, open a terminal and browse to the fruit-stand-redux-with-react-official-bindings folder. Run the command npm install to install the project’s dependencies. Then use the command npm start to run the Fruit Stand application.

This Fruit Stand example application is a React application created by the Create React App tooling. When running the application using npm start, the application should automatically open in your default browser. If it doesn’t, you can manually browse to http://localhost:3000/ to view the application.

What you learned

In this article, you learned how to use the React-Redux library’s connect function to give a component access to a Redux store.


Selectors

Selectors are functions used to extract and format information from the application state in different forms. When you finish this article, you should be able to write a selector to extract and format information from state stored in a Redux store.

Writing a selector

Here’s a sample state tree from the Fruit Stand React/Redux application:

{
  fruit: [
    'APPLE',
    'APPLE',
    'ORANGE',
    'GRAPEFRUIT',
    'WATERMELON',
  ],
  farmers: {
    1: {
      id: 1,
      name: 'John Smith',
      paid: false,
    },
    2: {
      id: 2,
      name: 'Sally Jones',
      paid: false,
    },
  }
}

The state’s farmers are stored as an object. Keys correspond to farmer.ids and values correspond to farmer objects. This yields O(1) for the lookup of a single farmer. However, storing all the farmers as values of an object makes it slightly inconvenient to obtain and render them all at once. To solve this inconvenience, we use selectors.

Selectors are typically defined in a file that sits next to the reducer for its slice of state. For example, if the farmers state slice is managed by the reducer defined in ./src/reducers/farmersReducer.js, then the farmers selectors would be stored in a file at ./src/reducers/farmersSelectors.js.

Selectors are passed the application’s state and return information from the state in a specified form (e.g. an array). You can use selectors to format different slice(s) of the state by calling them in a container’s mapStateToProps.

For example, getAllFarmers returns all the farmers stored in the state as an array of farmer objects, making it easier to iterate over and render each one.

// ./src/reducers/farmersSelectors.js

export const getAllFarmers = ({ farmers }) => (
  Object.values(farmers)
);

A selector can be used in multiple components’ mapStateToProps. For example:

// ./src/components/FarmerManagerContainer.js

import { getAllFarmers } from '../reducers/farmersSelectors';

const mapStateToProps = (state) => ({
  farmers: getAllFarmers(state),
});

Selectors are passed the entire application state so they can utilize multiple slices of the application state to assemble data. For example, if the Fruit Stand application’s state tree included a filter state slice:

{
  fruit: [
    'APPLE',
    'APPLE',
    'ORANGE',
    'GRAPEFRUIT',
    'WATERMELON',
  ],
  farmers: {
    1: {
      id: 1,
      name: 'John Smith',
      paid: false,
    },
    2: {
      id: 2,
      name: 'Sally Jones',
      paid: false,
    },
  },
  filter: ''
}

Then you could write a selector to extract a filtered list of farmer objects:

// ./src/reducers/farmersSelectors.js

export const getAllFarmers = ({ farmers }) => (
  Object.values(farmers)
);

export const getFilteredFarmers = ({ farmers, filter }) => {
  const lowerCaseFilter = filter.toLowerCase();
  return Object.values(farmers).filter(
    (farmer) => farmer.name.toLowerCase().includes(lowerCaseFilter)
  );
};
// ./src/components/FarmerManagerContainer.js

import { getAllFarmers, getFilteredFarmers } from '../reducers/farmersSelectors';

const mapStateToProps = (state) => ({
  farmers: getAllFarmers(state),
  filteredFarmers: getFilteredFarmers(state),
});

Selector examples

// ./src/reducers/farmersSelectors.js

// Returns the state's farmers as an array of farmer objects.
export const getAllFarmers = ({ farmers }) => (
  Object.values(farmers)
);

// Returns the state's farmers as an array of farmer objects,
// filtered by their name.
export const getFilteredFarmers = ({ farmers, filter }) => {
  const lowerCaseFilter = filter.toLowerCase();
  return Object.values(farmers).filter(
    (farmer) => farmer.name.toLowerCase().includes(lowerCaseFilter)
  );
};

// Returns the selected farmer object or an empty farmer object
// if no farmer exists with given id.
export const selectFarmer = ({ farmers }, id) => {
  const nullFarmer = {
    id: null,
    name: '',
    paid: false
  };
  return farmers[id] || nullFarmer;
};

What you learned

In this article, you learned how to write a selector to extract and format information from state stored in a Redux store.


Middleware

In Redux, middleware specifically refers to an enhancer passed to the store via createStore. When a dispatch is made, the middleware intercepts the action before it reaches the reducer. The middleware can then:

You’ll use Redux middleware for logging information about the store and making asynchronous API requests, but you can also use it for crash reporting, routing, and many other applications.

When you finish this article, you should be able to use the React-Redux library’s applyMiddleware function to configure one or more middleware when creating a store.

Applying middleware to a Redux store

Recall the redux library’s createStore function used to instantiate a store. createStore accepts three arguments (reducer, preloadedState, enhancer); middleware is given to the store via the optional enhancer argument.

Consider the following example, where you import a third-party logger middleware:

// ./src/store.js

import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';

import rootReducer from './reducers/rootReducer';

const configureStore = (preloadedState = {}) => {
  return createStore(
    rootReducer,
    preloadedState,
    applyMiddleware(logger),
  );
};

export default configureStore;

Any actions dispatched to the store pass through the logger middleware, which prints the store’s state before and after the action is processed.

Note: applyMiddleware() accepts multiple arguments, so you can also apply more middleware if necessary.

Reviewing the signature of middleware functions

In addition to importing third-party middlewares such as the above logger, you’ll sometimes need to roll your own. All middleware functions need to conform to the same signature in order to be compatible with the store and other middlewares.

A function signature is the set of inputs and output of a function. A Redux middleware must always have the following signature:

const middleware = store => next => action => {
 // side effects, if any
 return next(action);
};

Every middleware receives the store as an argument and returns a function that takes the next link in the middleware chain as an argument. That function returns another function that receives the action and then triggers any side effects before returning the result of next(action). Side effects can include triggering AJAX requests, logging to the console, and more. Side effects can also happen after next(action) is called, like so:

const middleware = store => next => action => {
 const result = next(action);
 // side effect using `result`
 return result;
};

Creating your own logger middleware

You can hand-roll the logger middleware you imported above. It should print out the state before and after each dispatch, allowing you to check if your reducers are working as expected. This middleware should:

The body of the innermost function is where you want to do your logging. That function should:

const logger = store => next => action => {
  console.log('Action received:', action);
  console.log('State pre-dispatch:', store.getState());

  let result = next(action);

  console.log('State post-dispatch:', store.getState());

  return result;
};

Now, whenever you dispatch an action, you’ll see its effect on the store.

Installing and applying the redux-logger middleware

As you move forward with Redux, you’ll want to have access to your store’s state for debugging purposes. Including the redux-logger npm package and adding it as a middleware gives you access (through the console) to the previous state, action, and next state with each dispatch. This is incredibly convenient for debugging purposes and avoids such unpleasantness as attaching the store to the window.

react-redux-logger
react-redux-logger

Follow the example below to include it in your projects:

npm install redux-logger

Note: logger must be the last middleware passed into applyMiddleware, otherwise it will log the thunk and any involved promises. You’ll learn about thunks and react-thunk in the next article.

// ./src/store.js

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';

import rootReducer from './reducers/rootReducer';

const configureStore = (preloadedState = {}) => {
  return createStore(
    rootReducer,
    preloadedState,
    applyMiddleware(thunk, logger),
  );
};

export default configureStore;

What you learned

In this article, you learned how to use the React-Redux library’s applyMiddleware function to configure one or more middleware when creating a store.


Thunks

One of the most common problems you need middleware to solve is asynchronicity. When building web applications that interact with a server, you’ll need to request resources and then dispatch the response to your store when it eventually gets back.

While it’s possible to make these API calls from your components and dispatch synchronously on success, for consistency and reusability it’s preferable to have the source of every change to our application state be an action creator. Thunks are a new kind of action creator that will allow you to do that.

When you finish this article, you should be able to write a thunk action creator to make an asynchronous request to an API and dispatch an action when the response is received.

Looking at how thunks work

Rather than returning a plain object, a thunk action creator returns a function. This function, when called with an argument of dispatch, can then dispatch one or more actions, immediately, or later. Here’s an example:

const thunkActionCreator = () => dispatch => {
  dispatch({
    type: 'RECEIVE_MESSAGE',
    message: 'This will be dispatched immediately.'
  });

  setTimeout(() => dispatch({
    type: 'RECEIVE_MESSAGE',
    message: 'This will be dispatched 1 second later.'
  }, 1000));
}

This is great, but without custom middleware it will break as soon as the function action hits your reducer. You need middleware to intercept all actions of type function and then trigger the dispatch:

// ./src/middleware/thunkMiddleware.js

const thunk = ({ dispatch, getState }) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }
  return next(action);
};

export default thunk;

Notice how the getState function is passed into the action in case your asynchronous action creators need access to your application state.

Then you’d apply your custom middleware to your store:

// ./src/store.js

import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';

import rootReducer from './reducers/rootReducer';
import thunk from './middleware/thunkMiddleware';

const configureStore = (preloadedState = {}) => {
  return createStore(
    rootReducer,
    preloadedState,
    applyMiddleware(thunk, logger),
  );
};

export default configureStore;

That’s it! Now that you have all the pieces, you’re ready to review a more concrete example.

Reviewing a concrete example

Much like the logger from the previous article, thunk middleware is available as the redux-thunk library.

The middleware you just wrote is almost the entire original library! (Check out the source code). For more on thunks and handling asynchronicity in Redux, you can take a look at this interesting SO post from the creator.

Start by using npm to install the redux-thunk package:

npm install redux-thunk

Then apply the middleware to your store:

// ./src/store.js

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';

import rootReducer from './reducers/rootReducer';

const configureStore = (preloadedState = {}) => {
  return createStore(
    rootReducer,
    preloadedState,
    applyMiddleware(thunk, logger),
  );
};

export default configureStore;

Imagine that you’re updating the Fruit Stand application to use a Node/Express API for data persistence. You would use a fetchFruits thunk action creator to retrieve the list of fruits from the API:

// ./src/actions/fruitActions.js

import { FRUIT_STAND_API_BASE_URL } from '../config';

export const RECEIVE_FRUITS = 'RECEIVE_FRUITS';

export const fetchFruits = () => (dispatch) => (
  fetch(`${FRUIT_STAND_API_BASE_URL}/fruits`)
    .then((res) => res.json())
    .then((data) => {
      dispatch(receiveFruits(data.fruits));
    })
);

const receiveFruits = (fruits) => {
  return {
    type: RECEIVE_FRUITS,
    fruits,
  };
};

Notice that the Fetch API is used to make an HTTP request to the /fruits API endpoint. When the promise returned from the fetch method call resolves, the res.json method is called to parse the JSON into JavaScript objects, which in turn is dispatched to the store using the receiveFruits action creator. The receiveFruits action creator returns an action of type RECEIVE_FRUITS that includes the fruit payload.

In the fruitReducer, the RECEIVE_FRUITS case clause simply returns the action.fruits payload as the new state:

// ./src/reducers/fruitReducer.js

import { RECEIVE_FRUITS } from '../actions/fruitActions';

const fruitReducer = (state = [], action) => {
  Object.freeze(state);
  switch (action.type) {
    case RECEIVE_FRUITS:
      return action.fruits;
    default:
      return state;
  }
};

export default fruitReducer;

To load the fruits from the API when the React application starts up, you can update the index.js file to dispatch the fetchFruits thunk action creator after creating the store:

// ./src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import './index.css';
import App from './App';
import configureStore from './store';
import { fetchFruits } from './actions/fruitActions';

const store = configureStore();
store.dispatch(fetchFruits());

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

Adding configuration for the API base URL

The FRUIT_STAND_API_BASE_URL variable (imported at the top of the fruitActions.js file) is defined in the config.js file:

export const FRUIT_STAND_API_BASE_URL = process.env.REACT_APP_FRUIT_STAND_API_BASE_URL;

And the REACT_APP_FRUIT_STAND_API_BASE_URL environment variable is defined in an .env file (located in the root of the React project):

REACT_APP_FRUIT_STAND_API_BASE_URL=http://localhost:8080

Adding configuration for the API base URL keeps you from having to hard-code a value that’ll change between environments.

Reviewing a completed Fruit Stand example

To review and run a completed Fruit Stand example application that utilizes middleware and thunks to support asynchronous interaction with a backend API, clone the redux-fruit-stand-examples repo.

After cloning the repo, open a terminal and browse to the fruit-stand-redux-with-react-middleware-thunks folder.

Running the API

To run the Node/Express API application, complete the following steps:

  1. Within the backend folder, add an .env file based upon the .env.example file.

  2. Use the following SQL statements to create a PostgreSQL database and user:

create database fruit_stand;
create user fruit_stand_app with encrypted password '«a strong password for the fruit_stand_app user»';
grant all privileges on database fruit_stand to fruit_stand_app;
  1. From a terminal, browse to the backend folder and run the following commands to apply the Sequelize migrations and seed data:
npx dotenv sequelize db:migrate
npx dotenv sequelize db:seed:all
  1. Start the application using npm start.

Running the React application

From the frontend folder, run the command npm install to install the project’s dependencies. Then use the command npm start to run the Fruit Stand application.

This Fruit Stand example application is a React application created by the Create React App tooling. When running the application using npm start, the application should automatically open in your default browser. If it doesn’t, you can manually browse to http://localhost:3000/ to view the application.

What you learned

In this article, you learned how to write a thunk action creator to make an asynchronous request to an API and dispatch an action when the response is received.


Advanced Containers

While you learned in an earlier article that you should aim to have very few containers, there are exceptions. When you finish this article, you should be able to describe a situation where defining multiple containers for a single component is advantageous.

Knowing when to break the rules

Separating your concerns with presentational and container components allows you to reuse presentational components where it makes sense, rather than duplicating code. If a presentational component needs different data in each situation, though, you may need more containers. By creating more container components, you can render the same presentational component with each of those containers to suit different needs.

Consider a form component that may either create or edit a post. The form itself looks and works the same in both cases; it has a few inputs and a submit button. The use cases differ, though, in that the edit form needs to map state from the store to its props, while the create form does not. Furthermore, the edit form will need to dispatch a different action when the form submits than the create form will, as well as request the object from our backend.

As you go through the code snippets below, read the comments carefully.

Here’s the presentational component, PostForm:

// PostForm.js

import React from 'react';

class PostForm extends React.Component {
  constructor(props) {
    super(props);
    // set up initial state
    this.state = this.props.post; // a Post object has a title and a body
  }

  static getDerivedStateFromProps(props, state) {
    // if we get a different post in props, we'll need to set state
    if (props.post.id !== state.id) {
      return props.post;
    }
  }

  update = (field) => {
    return (e) => {
      this.setState({ [field]: e.target.value });
    };
  }

  handleSubmit = (e) => {
    e.preventDefault();
    // `submit` will be a thunk action that presumably creates or edits a post
    this.props.submit(this.state);
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Title
          <input
            type="text"
            onChange={this.update("title")}
            value={this.state.title}
          />
        </label>

        <label>
          Body
          <input
            type="text"
            onChange={this.update("body")}
            value={this.state.body}
          />
        </label>

        <button>Submit Post</button>
      </form>
    );
  }
}

export default PostForm;

You can see that PostForm is expecting two things in props: a post object and a submit function. The container will have to define these, since right now, this form can’t actually do anything. Give it the ability to create a post:

// CreatePostFormContainer.js

import { connect } from 'react-redux';
import PostForm from './PostForm';
import { createPost } from '../actions/postActions';

const mapStateToProps = state => {
  return {
    post: { title: '', body: '' } // a default blank object
  };
};

const mapDispatchToProps = dispatch => {
  return {
    submit: post => dispatch(createPost(post))
  };
};

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

So far, this is nothing new. Now, wherever you need a form to create a post, you can render CreatePostFormContainer by importing from the above file.

But what about editing? This is a little trickier, because you need more information from the store - so you’ll need a higher-order component to help you out. Higher-order components are a useful React pattern that essentially uses a component to render another component, usually to handle some sort of work and pass in data. This pattern allows us to keep your components small and modular. Here, you’ll use a higher-order component to fetch the post you want to edit and pass it into the PostForm:

// EditPostFormContainer.js

import React from 'react';
import { connect } from 'react-redux';
import PostForm from './PostForm';
import { fetchPost, updatePost } from '../actions/postActions';
import { selectPost } from '../reducers/postSelectors';

const mapStateToProps = (state, ownProps) => {
  const defaultPost = { title: '', body: '' };
  const post = selectPost(ownProps.match.params.postId) || defaultPost;
  // get the post this route is asking for
  // (assuming here that this component is being rendered by a route)
  // if you don't have the post in state yet, return a blank post so PostForm doesn't break
  return { post };
};

const mapDispatchToProps = dispatch => {
  // an edit form will need to fetch the relevant post, but the PostForm shouldn't handle that
  // you'll handle this problem with a higher-order component, EditPostFormContainer
  return {
    fetchPost: id => dispatch(fetchPost(id)),
    submit: post => dispatch(updatePost(post))
  };
};

class EditPostForm extends React.Component {
  // this is the higher-order component made to handle the fetch

  componentDidMount() {
    // do the fetching here so that PostForm doesn't have to
    this.props.fetchPost(this.props.match.params.postId);
  }

  render() {
    // destructure the props so you can easily pass them down to PostForm
    const { post, submit } = this.props;
    return <PostForm post={post} submit={submit} />;
  }
}

// now `connect` it to the Redux store

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

The result here is that we can render a CreatePostFormContainer wherever you want a form to create a post, and an EditPostFormContainer wherever you want to edit a post. Both components will render a PostForm, but each will have different functions. The PostForm also gets to be very simple and make almost no decisions. This helps keep your code DRY and modular.

You can use this pattern with any presentational component that needs to be connected to the store, but may need entirely different data to perform different functions.

What you learned

In this article, you learned about a situation where defining multiple containers for a single component is advantageous.


Redux Developer Tools

Redux has its own special set of developer tools. They allow you to do things like inspect your application state in real time as you use your app, or cancel an action to see a live recalculation of the state as if that action had never been dispatched. They require only a few minutes of setup, and can be well worth the effort.

Instructions

  1. Install the chrome extension.

  2. Install the npm package into your project:

npm install redux-devtools-extension
  1. Make the following changes to your ./src/store.js file.

If you’re not using middleware:

  // ./src/store.js

  import { createStore } from 'redux';
+ import { devToolsEnhancer } from 'redux-devtools-extension';

  import rootReducer from './reducers/rootReducer';

  const configureStore = () => {
    return createStore(
      rootReducer,
+     devToolsEnhancer()
    );
  };

  export default configureStore;

Or if you’re using middleware:

  // ./src/store.js

  import { createStore, applyMiddleware } from 'redux';
  import thunk from 'redux-thunk';
  import logger from 'redux-logger';
+ import { composeWithDevTools } from 'redux-devtools-extension';

  import rootReducer from './reducers/rootReducer';

  const configureStore = () => {
    return createStore(
      rootReducer,
+     composeWithDevTools(applyMiddleware(thunk, logger))
-     applyMiddleware(thunk, logger)
    );
  };

  export default configureStore;

Use

Now that you’ve set up the Redux dev tools, you can try them out. You’ll use one of the Fruit Stand application examples. If you haven’t already, clone the redux-fruit-stand-examples repo.

After cloning the repo, open a terminal and browse to the fruit-stand-redux-with-react-official-bindings folder. Run the command npm install to install the project’s dependencies. Then use the command npm start to run the Fruit Stand application.

This Fruit Stand example application is a React application created by the Create React App tooling. When running the application using npm start, the application should automatically open in your default browser. If it doesn’t, you can manually browse to http://localhost:3000/ to view the application.

Open the project into your code editor and complete the above set up steps.

You should see an atom (a nucleus with electrons) icon on your Chrome toolbar, and if you’ve set up the Redux dev tools correctly it should now be green. Click on it. When the Redux dev tools open, click one of the buttons on the very bottom left to open them in a new window.

Now try adding some fruit. This will cause actions to be dispatched. You should see those actions popping up in the Redux dev tools. You can click on them to cancel them and you should see the state recalculated in real time.

The Redux dev tools have some other handy features, so click around and explore!

Resources


Redux-Based Pokedex Project

Instead of building a new application, you will spend time refactoring an existing application, one that is not yours. Navigating around someone else’s code is an interesting way to learn what to do and what not to do. The starter application is in a little bit of a mess and needs your help to get unmessed.

By the end of this walk-through, you will be able to:

Getting started

You’ll need the backend of the application.

If you have not cloned it, please clone the repository from https://github.com/appacademy-starters/pokedex-backend.

If you have cloned it, please get the latest (things sometimes change) by going to the repository in your Terminal and typing git pull origin master. This will get the latest code for you.

You’ll need the starter application. Please clone the repository from https://github.com/appacademy-starters/redux-pokedex-starter.

Tour the application

This application is a Heroku-deployable React application. Here are the files that are in it. Hopefully, you find nothing surprising about the file layout or the intent of each file. Take a moment to look in each file to get the lay of the land. (Really, take a look in each file. You may feel like jumping right into it, but looking at other people’s code is helpful. This is one of the benefits to the pair-programming learning style at App Academy.)

.
├── package-lock.json        - The NPM lock file
├── package.json             - The NPM file
├── public
│   └── index.html           - The page that gets served to the browser
├── server.js                - A very light-weight server for Heroku
└── src
    ├── App.js               - The main application component
    ├── Fab.js               - A floating action button component
    ├── LoginPanel.js        - The form that shows the login
    ├── LogoutButton.js      - A component for logging out
    ├── PokemonBrowser.js    - The component that shows the list and detail
    ├── PokemonDetail.js     - The component that shows the detail of a Pokemon
    ├── PokemonForm.js       - The form to create a new Pokemon
    ├── config.js            - Configuration variables
    ├── index.css            - Styling for the application
    └── index.js             - The main entry point for Webpack

Start the backend with npm run dev. Start the React application with npm run dev. Make sure it runs. It looks for a local backend at http://localhost:8000, so make sure that’s where the backend is running.

Install Redux and DevTools

If you haven’t already, install Redux DevTools. During development, you can watch the Redux store handle actions and change state in the timeline.

To use Redux in this application, you need to install it and the connector between Redux and React. You will also want to use asynchronous actions with the Redux store, so you’ll want a middleware, one like Redux Thunk.

npm install react-redux redux redux-thunk

There are more than one asynchronous action-handling middleware out there in the world. Redux Thunk happens to be one of the oldest and widely used.

Whenever you consider installing a library or framework, you should make sure that your existing application meets the expectations. For example, as of the time of this writing, to install “react-redux”, your application needs to support React 16.8.3 or later. Take a look in the package.json file to make sure that an acceptable version of React is listed in there. If not, you will need to upgrade the version of React used by this project by running something like npm upgrade.

Setting up the store

The store is the object (and supporting objects and functions) that will contain the state of the application. This centralizes the state so that, presumably, you can better reason about it.

There are a couple of ways to organize your state management code, each with their own benefits. Redux has a list of different articles about this very topic. You should choose to organize your code in a way that makes sense to your team (or follow any conventions that already exist). This walk-through will follow the Ducks approach of layout.

In the src directory, create a new directory named store. In that new directory, create a new file named configureStore.js. In that file, put this code. This is boilerplate code and will appear in nearly every application that you have that uses Redux. A description of the contents follows the code block.

import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
import thunk from 'redux-thunk';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const reducer = combineReducers({
});

const configureStore = initialState => {
  return createStore(
    reducer,
    initialState,
    composeEnhancers(applyMiddleware(thunk)),
  );
};

export default configureStore;

(If you’re looking in your console after putting this code in there, you’ll see an error about not having any valid reducers. That’s true, there are no valid reducers. You’ll fix that in just a moment.)

On those first two lines, the code imports the stuff it will need to create and configure a store.

The fourth line creates a new composeEnhancers variable that will be either the Redux DevTools special compose function, or the one from Redux, if DevTools is not installed. This allows browsers that have the DevTools installed to take advantage of watching the changes in the store.

The sixth and seventh lines is just the thing that combines the reducers for the store into a single reducer. More about that later.

The ninth line declares a function that gets exported at the end of the file that creates and configures the store with the reducer, the initialState passed into the function, and composes the React Thunk middleware and the Redux DevTools, if they exist.

To use this functionality, open src/index.js and add two imports to the top of the file.

import { Provider } from 'react-redux';
import configureStore from './store/configureStore';

Those two lines import a Context Provider from React Redux and the function you just created above. Now, under those lines and before the ReactDOM.render statement, create a store.

const store = configureStore();

Finally, wrap the App component in a Provider component that has a “store” property assigned the value of store that you created in the previous code snippet, just as you saw in the Passing the Store section of the Usage with React article you read in the homework preparing you for these topics.

If everything works, you should be able to view the Redux DevTools in your browser’s DevTools environment as a new tab. You should see the “@@INIT” action that was run and an empty state.

Redux DevTools initialization
Redux DevTools initialization

Your first action workflow

Now, you will refactor the LoginPanel component to put the token it receives from the AJAX call into the Redux store. The rest of the application will remain the same, for now.

Open LoginPanel and review it. There are two “interesting” portions of this code, the render function which will redirect to the path “/” when a token exists (and show the login form if it does not), and the handleSubmit method that makes the actual AJAX call to get the token. These are the things that you will modify to make this work. It’s going to seem like a lot of code replacing just a few lines, but this will make your application easier to understand because all of the state changes will be encapsulated in their own area under the store directory. You won’t have to search through a bunch of components to figure out why the application as a whole is not working.

The first step is to make LoginPanel a connected component, that is, connect it to the Redux store’s pipeline. To do this, follow these steps:

  1. Import connect from react-redux in the import section of the LoginPanel.
  2. Remove the export line at the bottom and replace it with this.

The last line of the code connects the LoginPanel to the Redux store. The two functions that you created above it, mapStateToProps and mapDispatchToProps are functions that you will write to help in translating state and actions for use in your component. The mapStateToProps function maps the state of the Redux store to the values that you want to show up in the props of the connected component. The mapDispatchToProps function maps complicated action calls to simple ones that your component can use.

The first thing you’ll need is one of those actions. This is going to be something to let your authentication run to get that token. In the src/store directory, create a new file named authentication.js which will contain all of the Redux-related stuff to handle authentication:

The steps that you want the application to go through are:

Most of these steps will need something more, so if you see something not defined in one of these steps, you will get to it in a later step.

The thunk

In case you lost track, this goes in src/store/authentication.js.

First up, thunks are functions that return another function that takes a single function as its argument. The argument is the dispatch method used to dispatch actions to Redux. So, for example, the login thunk could have this form.

export const login = (email, password) => async dispatch => {
  // Dispatch an action, here
}

That’s pretty weird, if you’ve never seen that syntax before, those double => signs in there. It’s a shortcut to write a function that returns a function. You could also write it like this:

export const login = (email, password) => {
  return async dispatch => {
    // Dispatch an action, here
  }
}

Once you get used to the double (or triple) => signs, it becomes second nature to write functions that return functions, that way.

Inside the login method, make a fetch call to the API using the same fetch call found in LoginPanel’s handleSubmit method. You’ll need to import baseUrl from the src/config.js module. You don’t have access to the state of the object, just the email and password that they’re passing in through your method call, so change the “body” parameter of the fetch from JSON.stringify(this.state), to JSON.stringify({ email, password }),.

For now, if the response from the fetch is ok, just get the token out of the response object and log it to the console.

Now, to hook up this thunk to your component, open src/LoginPanel.js and import the login thunk that you just created. At the bottom in the mapDispatchToProps function, add a “login” key that is a function that takes an email and password, and then dispatches the login thunk with those values.

return {
  login: (email, password) => dispatch(login(email, password))
};

Now, there’s a “login” property on the props handed to the LoginPanel component. The value of the “login” property is a function that takes an email and a password. Those then get handed to the login thunk which returns a function to the dispatch function of Redux. It’s functions all the way down.

Now, delete everything from the handleSubmit method except the line that prevents the default action. Replace it with a call to the function in the “login” property of the props like this.

this.props.login(this.state.email, this.state.password);

After the application refreshes, you should be able to click the Login button on the screen and see token appear after the AJAX call completes.

Now that you have the token, you need to dispatch another action, one that isn’t created yet, to set the token in the state so it can be used elsewhere. Remove the console.log statement in your login thunk and replace it with an invocation of the dispatch method that your thunk gets, dispatching an action creator named setToken with the token as its argument.

dispatch(setToken(token))

Now, it’s time to create that action.

The action

Actions are just plain objects that have, at a minimum, a “type” property. Action creators are just plain functions that return actions.

Below your import section and above the login thunk of your code in src/store/authentication.js, create a constant named SET_TOKEN and set it equal to the string 'pokedex/authentication/SET_TOKEN'. You could make this string anything, it just needs to be unique within your application. This is merely the Ducks convention.

Now, create a function setToken that takes a token as its one parameter, and returns an object that has a “type” property set to the SET_TOKEN constant and a “token” property set to the value passed into the parameter. Export the setToken function.

If you’ve done everything correctly to this point, when you click the Login button, you should now see an action appear in the Redux DevTools with the string that you set the SET_TOKEN constant to.

Redux DevTools with SET_TOKEN action
Redux DevTools with SET_TOKEN action

Now, you need to tell Redux how to handle that action with a reducer.

The reducer

Somewhere in the src/store/authentication.js file, export a reducer function as the default value of the module. The state parameter gets a default value of an empty object because Redux does not like undefined values returned from reducers. Redux will call your reducer when it creates the store with undefined just to mess with you.

export default function reducer(state = {}, action) {
  // Your code in here.
}

In that reducer, you want to check if the “type” property of action is equal to the SET_TOKEN constant. Every reducer is called with every action, so you have to do this check. If it is, then you should return an object with the token in it. If it doesn’t, then just return the state unaltered. Idiomatic Redux usually uses a switch statement to do this with each case statement handling a different action type.

export default function reducer(state = {}, action) {
  switch (action.type) {
    case SET_TOKEN: {
      return {
        ...state,
        token: action.token,
      };
    }

    default: return state;
  }
}

Adding the reducer to the store

Now that you have the reducer handling the action, you must add it to the reducer in src/store/configureStore.js. Open up that file and import the default value as authentication from the src/store/authentication.js module.

import authentication from './authentication';

Then, in the combineReducers invocation, add authentication as a key and value.

const reducer = combineReducers({
  authentication
});

After the page reloads, when you click the Login button, you should now see the authentication and token appear in the right pane of the Redux DevTools. (You may need to select the action in the left pane.)

Using the token

Now, you’ve come full circle. You’re going to use the token in the store to inform the LoginPanel that it needs to close. In the render function, the code uses this.state.token to determine whether or not to redirect. You want that to use this.props.token, now, so change it to that.

To get the token value from the state of the Redux store into the props, you use mapStateToProps function that you created below. Change it to read like this, now, which takes the value of the token stored in the state and puts it into the “token” property of what will be passed to LoginPanel in its props.

const mapStateToProps = state => {
  return {
    token: state.authentication.token,
  };
};

Now, when you click the Login button, it redirects the application back to “/”. However, because App relies on its own state to get updated from a call to its updateToken method, it goes into an recursive loop of redirecting back and forth between the App component and the LoginPanel. You’ll fix that in the next section.


More State Moving

The application is cleaner, but now has that bug of infinite redirecting. You will first fix that.

Fixing the loop

In the App component, it would change state in response to the LoginPanel calling the updateToken method. The LoginPanel no longer does that because it dispatches its information to a thunk that makes the AJAX call and, in turns, dispatches an action that updates the Redux store. Then, LoginPanel uses the value from the store to no that something good has happened. You need to have App do the same.

Since updateToken is no longer used, remove the method and all of its uses from the App component.

When you have that done, you may notice that the route that shows the LoginPanel looks like this.

<Route path="/login"
  render={props => <LoginPanel {...props} />} />

That is rendering LoginPanel and passing its props to it. That’s just like using the “component” property of the Route component, so change it to use that instead of the more expensive “render” property.

<Route path="/login" component={LoginPanel} />

You’re still in a render loop, so that hasn’t fixed it. Time to connect App to the Redux store.

  1. Like you did in LoginPanel, import the connect function from the “react-redux” module.
  2. Then, just like you did in LoginPanel, declare mapStateToProps and mapDispatchToProps functions after the component.
  1. Finally, connect App to the Redux store using those functions.

That puts the value of the token into the props. In the actual App component, now find everywhere that uses this.state.token and replace it with this.props.token.

Try logging in, again. Still doesn’t work. That’s because App relies on another setting in its state named “needLogin” which is just a Boolean value that is basically the opposite of whether the token has a value. If there is a token, there is no need to login. If there is no token, there is a need to login. You can figure that out in the mapStateToProps function! Create another property, one named “needLogin”, in the object returned from the function. If there is a value in state.authentication.token, then set it to false. If there is no value (or an empty string) in state.authentication.token, then set “needLogin” to true. Once you have that, find every use of this.state.needLogin and replace it with this.props.needLogin.

You have now fixed the problem with the infinite redirects! And, once you log into the application, you can check that the App component is rendering the PokemonBrowser component. However, it is not showing anything. That’s because the App component calls its loadPokemon method in the componentDidMount method. There’s no token at that time in its state because the token now comes from Redux.

What would be great is if, after logging in, the login action loaded the Pokemon for you, put them in the store, and PokemonBrowser could just use them directly.

Oh, that’s what you’ll do next.

Getting the list of Pokemon

Now, you need some actions, thunks, and reducers for Pokemon, not authentication. Create a new file src/store/pokemon.js. In there, create the following items.

Now, you need to make a thunk that will make the AJAX call. Call your think “getPokemon”. The thunk needs the token from the state to make its API call. The function that gets the “dispatch” parameter can also get a second parameter, a function conventionally called “getState”. Your thunk could look like this.

export const getPokemon = () => async (dispatch, getState) => {
  const { authentication: { token } } = getState();
  // AJAX call
  // Handle response
};

Then, in configureStore.js, import the default reducer and add it to the combineReducers argument as the “pokemon” property next to “authentication”.

const reducer = combineReducers({
  authentication,
  pokemon,
});

Now, you just need to kick off that AJAX call. It seems only reasonable that the component that actually needs it kicks it off, the PokemonBrowser component. In PokemonBrowser, do the following:

Now, when you log into the application, you should see two actions in your Redux store!

Pokemon LOAD action in Redux DevTools
Pokemon LOAD action in Redux DevTools

More importantly, you should see the list of Pokemon in the PokemonBrowser actually appear in the browser.

Clean up the App component

Previously, all of the fetching logic for the list of Pokemon was handled by the App component. You can now get rid of the loadPokemon method and clean up any calls to it. Also, anywhere that refers to this.state.pokemon or using setState to update the “pokemon” property, you should get rid of all of that. This makes the handleCreated method empty, so get rid of that and all references to it, too, in the App component.

Because the App component doesn’t make any AJAX calls, anymore, it doesn’t need the token from the state. Remove the “token” property in mapStateToProps and everywhere token is used in the App component. Include the call to local storage, too, in the deleting of things. You can remove the import of baseUrl, too, because there are no AJAX calls in the file.

Now, because cProps in the render method is empty, delete it and its uses in the PrivateRoute components below it. Now that the code is not using cProps, you can delete the parameter and its use from the PrivateRoute component on line 8 (or so) of the src/App.js file.

Storing the token in local storage

Actions that use outside resources like AJAX calls and local storage must be created in thunks. Back in the src/store/authentication.js module, do two things:

Reading the token out of local storage

You need a new thunk to do this. Create a thunk named loadToken that takes no parameters, and returns a function that accepts a “dispatch” parameter. Then, implement it to read the value from local storage using the TOKEN_KEY constant. If a value comes back, have it dispatch the setToken action.

export const loadToken = () => async dispatch => {
  // Read the token from local
};

In the App component, import the loadToken thunk. Use it in the mapDispatchToProps by mapping the dispatch of the loadToken thunk to a property of the same name.

const mapDispatchToProps = dispatch => {
  return {
    loadToken: () => dispatch(loadToken()),
  };
};

Invoke that loadToken method in the componentDidMount method of the App component.

async componentDidMount() {
  this.setState({ loaded: true });
  this.props.loadToken();
}

The moment you do that, the page should refresh and you should see the Pokemon browser rather than the login form.

You’re halfway home!


The application is cleaner, but now has that bug of infinite redirecting. You will first fix that.

Fixing the loop

In the App component, it would change state in response to the LoginPanel calling the updateToken method. The LoginPanel no longer does that because it dispatches its information to a thunk that makes the AJAX call and, in turns, dispatches an action that updates the Redux store. Then, LoginPanel uses the value from the store to no that something good has happened. You need to have App do the same.

Since updateToken is no longer used, remove the method and all of its uses from the App component.

When you have that done, you may notice that the route that shows the LoginPanel looks like this.

<Route path="/login"
  render={props => <LoginPanel {...props} />} />

That is rendering LoginPanel and passing its props to it. That’s just like using the “component” property of the Route component, so change it to use that instead of the more expensive “render” property.

<Route path="/login" component={LoginPanel} />

You’re still in a render loop, so that hasn’t fixed it. Time to connect App to the Redux store.

  1. Like you did in LoginPanel, import the connect function from the “react-redux” module.
  2. Then, just like you did in LoginPanel, declare mapStateToProps and mapDispatchToProps functions after the component.
  1. Finally, connect App to the Redux store using those functions.

That puts the value of the token into the props. In the actual App component, now find everywhere that uses this.state.token and replace it with this.props.token.

Try logging in, again. Still doesn’t work. That’s because App relies on another setting in its state named “needLogin” which is just a Boolean value that is basically the opposite of whether the token has a value. If there is a token, there is no need to login. If there is no token, there is a need to login. You can figure that out in the mapStateToProps function! Create another property, one named “needLogin”, in the object returned from the function. If there is a value in state.authentication.token, then set it to false. If there is no value (or an empty string) in state.authentication.token, then set “needLogin” to true. Once you have that, find every use of this.state.needLogin and replace it with this.props.needLogin.

You have now fixed the problem with the infinite redirects! And, once you log into the application, you can check that the App component is rendering the PokemonBrowser component. However, it is not showing anything. That’s because the App component calls its loadPokemon method in the componentDidMount method. There’s no token at that time in its state because the token now comes from Redux.

What would be great is if, after logging in, the login action loaded the Pokemon for you, put them in the store, and PokemonBrowser could just use them directly.

Oh, that’s what you’ll do next.

Getting the list of Pokemon

Now, you need some actions, thunks, and reducers for Pokemon, not authentication. Create a new file src/store/pokemon.js. In there, create the following items.

Now, you need to make a thunk that will make the AJAX call. Call your think “getPokemon”. The thunk needs the token from the state to make its API call. The function that gets the “dispatch” parameter can also get a second parameter, a function conventionally called “getState”. Your thunk could look like this.

export const getPokemon = () => async (dispatch, getState) => {
  const { authentication: { token } } = getState();
  // AJAX call
  // Handle response
};

Then, in configureStore.js, import the default reducer and add it to the combineReducers argument as the “pokemon” property next to “authentication”.

const reducer = combineReducers({
  authentication,
  pokemon,
});

Now, you just need to kick off that AJAX call. It seems only reasonable that the component that actually needs it kicks it off, the PokemonBrowser component. In PokemonBrowser, do the following:

Now, when you log into the application, you should see two actions in your Redux store!

Pokemon LOAD action in Redux DevTools
Pokemon LOAD action in Redux DevTools

More importantly, you should see the list of Pokemon in the PokemonBrowser actually appear in the browser.

Clean up the App component

Previously, all of the fetching logic for the list of Pokemon was handled by the App component. You can now get rid of the loadPokemon method and clean up any calls to it. Also, anywhere that refers to this.state.pokemon or using setState to update the “pokemon” property, you should get rid of all of that. This makes the handleCreated method empty, so get rid of that and all references to it, too, in the App component.

Because the App component doesn’t make any AJAX calls, anymore, it doesn’t need the token from the state. Remove the “token” property in mapStateToProps and everywhere token is used in the App component. Include the call to local storage, too, in the deleting of things. You can remove the import of baseUrl, too, because there are no AJAX calls in the file.

Now, because cProps in the render method is empty, delete it and its uses in the PrivateRoute components below it. Now that the code is not using cProps, you can delete the parameter and its use from the PrivateRoute component on line 8 (or so) of the src/App.js file.

Storing the token in local storage

Actions that use outside resources like AJAX calls and local storage must be created in thunks. Back in the src/store/authentication.js module, do two things:

Reading the token out of local storage

You need a new thunk to do this. Create a thunk named loadToken that takes no parameters, and returns a function that accepts a “dispatch” parameter. Then, implement it to read the value from local storage using the TOKEN_KEY constant. If a value comes back, have it dispatch the setToken action.

export const loadToken = () => async dispatch => {
  // Read the token from local
};

In the App component, import the loadToken thunk. Use it in the mapDispatchToProps by mapping the dispatch of the loadToken thunk to a property of the same name.

const mapDispatchToProps = dispatch => {
  return {
    loadToken: () => dispatch(loadToken()),
  };
};

Invoke that loadToken method in the componentDidMount method of the App component.

async componentDidMount() {
  this.setState({ loaded: true });
  this.props.loadToken();
}

The moment you do that, the page should refresh and you should see the Pokemon browser rather than the login form.

You’re halfway home!


Making Decisions About State

Now, it’s time to log out of the application. Do that with the following steps.

The steps that the application will take are these:

  1. Someone clicks the logout button
  2. The LogoutButton component dispatches a thunk
  3. The thunk makes the AJAX call to logout
  4. If that AJAX call succeeds, remove the token from local storage and dispatch an action to remove the token from the store
  5. Redux will invoke a reducer that removes the token from the store
  6. The LogoutButton will redirect the application back to “/login”

In src/store/authentication.js:

In src/LogoutButton.js:

(If you make a mistake with this and get into an inconsistent state, just delete all of the contents of your local storage and refresh your browser.)

If you check the console, now, you’ll see that Babel is reporting a “useless” constructor. Sure enough, it is. LogoutButton no longer has any state, so there’s no reason to leave it as a class-based component. Convert it to a function-based component. If you’ve followed these instructions, your LogoutButton should end up looking something like this.

const LogoutButton = props =>
  props.loggedOut ?
    <Redirect to="/login" /> :
    <div id="logout-button-holder">
      <button onClick={props.logout}>Logout</button>
    </div>
;

The Rest Of It

You have now been given instructions on how to refactor components from managing global application state to putting it in Redux. There are two more pieces left, the “select the current Pokemon” functionality and the “create a new Pokemon” functionality. Refactor the application so those are Redux-supported, as well.

Select the current Pokemon

The place to start, here, is to determine how the click of the navigation item on the left gets handled. It’s a NavLink, so the BrowserRouter in the App component handles that by routing to the PokemonBrowser with the route parameters. The PokemonBrowser then routes to the PokemonDetail with a Route component. In the PokemonDetail component, if the value of the this.props.match.params.id changes, then the loadPokemon method is called which, in turn, makes an AJAX call. And, there it is! The AJAX call.

This is like everything else, create a thunk, an action type, yada yada yada.

Creating a new Pokemon

This is very similar to the login stuff you did with LoginPanel. In the PokemonForm, have

In moving the Pokemon type fetching from the state to the props, you may end up getting an error that there is no method “map” of undefined. If that’s the case, in the reducer in your src/store/pokemon.js file, have the default state include an array for the “types” property.

// CODE SNIPPET
export default function reducer(state = { types: [] }, action) {

That’s the power of default parameters and initial state!

The action types, action creators, and thunk created to do this should go into the src/store/pokemon.js module. When the AJAX call succeeds to create the new Pokemon, have it then dispatch the getPokemon thunk to get a new list of Pokemon. Redux and React will add a new Pokemon to the end of the list. That’s why you have to provide the “key” property in lists of things, so that React will efficiently determine if something in the list needs to get changed, added, or deleted.

The only “new” part, here, is the coordination between PokemonForm and PokemonBrowser to determine if it should show a form. This is up to you to decide, if showing the create form is part of the global application state (and should exist in the Redux store), or if it is part of the “local” state between the two components and be managed by PokemonForm invoking a function passed to it by PokemonBrowser. The solution choose the former solution.

Bonus: Extend the functionality

Think about adding

Bonus: Connected React Router

Rather than relying on Redirect routes in your application, you can use actions to manage the URL of your application. Install Connected React Router and remove all Redirect components from the application, replacing them with dispatched push actions. Check out the How to navigation with Redux action article in the Connected React Router documentation.


Giphy Search Project

Today’s project will help you become more comfortable with the full Redux cycle! You will build out a single Redux cycle for a Giphy search tool.

When a user enters a search query, a fetch action will use the search endpoint from the Giphy API to return the fetch response. The action will then be dispatched to update the application’s global state provided by the Redux store. Your application will then use a slice of state to render an index of GIF results.

Your completed project will look something like this:

giphy-search
giphy-search

Phase 0: Set up the project

You’ll begin by cloning this repository containing basic skeleton files and the conventional frontend folders (actions, components, reducers, and util).

git clone https://github.com/appacademy-starters/react-redux-giphy-starter.git

Take a moment to familiarize yourself with the file structure. Look inside all the frontend files and note the TODO notes in each file skeleton. Throughout the next phases of the project, you’ll finish all the tasks listed in the TODO notes to create your very own Giphy search app!

After you have reviewed the TODO notes, run npm install to install your application’s packages. Note that you have redux, react-redux, redux-thunk, and redux-logger already listed as dependencies in your project’s package.json file. You’ll use Redux thunk as middleware to connect (or dispatch) your fetch results into the Redux store. You’ll use Redux logger as middleware to automatically console log your dispatched actions.

Component overview

Now that you’re acquainted with the file structure, let’s map out an overview of the component hierarchy:

Root
  AppContainer
    App
      SearchBar
      Gifs

Giphy API key

Before you start, let’s create a Giphy API Key to use in your fetch requests to the Giphy API. Get started by creating a Giphy account. Then navigate to the Giphy API Quick Start Guide and click Create an App.

Fill out the form for creating a new app, and only check the option for I only want to use the GIPHY API.

Giphy Example
Giphy Example

Once you’ve submitted the form, you’ll be taken to a dashboard, and under the Your Apps section, you should see your newly created app with an API Key that you will use for this project. As a reminder, API keys normally shouldn’t be stored in client-side JavaScript. You would normally want to store the keys in your server-side code. To keep this project a simple, front-end only project, you’ll store the API key in a front-end environment variable for convenience. Take a moment to create an .env file in the root of your project and set an environment variable with your API key, like so:

REACT_APP_GIPHY_API_KEY=<<YOUR API KEY>>

Notice that your config.js file is already exporting your REACT_APP_GIPHY_API_KEY environment variable as apiKey:

export const apiKey = process.env.REACT_APP_GIPHY_API_KEY

This means that you can import the API key from any of your frontend components with the following import statement:

import { apiKey } from '../config';

Phase 1: Fetch data in the Redux cycle

Before you begin to build the project, it’s important to think about the state shape. You know that you want to display GIF results returned by a fetch request. This means you’ll probably want a gifs slice of the state that holds a collection of gif objects.

State shape

{
  gifs: [
    // gif objects  
  ]
}

As a reminder, you pass the gifs slice of state as a prop to the App component in the AppContainer through the mapStateToProps function.

API util

The first part of creating your Redux cycle for fetching GIFs is creating a fetch request that will be connected to a thunk action creator. Define and export a fetchGifs function in the apiUtil.js file. This function will make a fetch call to the Giphy API’s search endpoint.

// apiUtil.js
import { apiKey } from '../config';

export const fetchGifs = searchTerm => (
  // TODO: Write a fetch call to the Giphy API's search endpoint
)

It will take a single argument, the searchQuery entered by a user. You can check out the Giphy API docs for more details, but in short, you want to make a fetch request to the following endpoint:

`http://api.giphy.com/v1/gifs/search?api_key=${apiKey}&q=${searchTerm}&limit=3`

The searchQuery will be replaced with your user’s actual query. You should tag &limit=3 onto the end of your query string to tell Giphy you only want three GIF responses. The Giphy API is relatively slow, so keeping the response size down helps optimize your application’s performance.

Remember, it’s best to test small pieces as we go. Let’s test out that fetch request from your developer tools console to make sure it’s doing what you’re intending.

You may need to restart your server to have your application process the environment variables set in your .env file.

Import your fetchGifs function to the entry index.js file, then go ahead and put it on the window so we have access to it in the console:

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './components/Root';
import { fetchGifs } from './util/apiUtil';

window.fetchGifs = fetchGifs;

ReactDOM.render(
  <React.StrictMode>
    <Root />
  </React.StrictMode>,
  document.getElementById("root")
);

Try running this test code:

fetchGifs('puppy').then(res => res.json()).then(res => console.log(res.data));

This will make the fetch request, which will return a promise. You’ll chain on a .then to parse the response and log the parsed response data. You should see an array of three objects. Those are your gif objects fetched from the Giphy API! Make sure you get this working before moving on, and don’t forget to remove fetchGifs from the window once you’re done testing.

Phase 2: Redux actions

Next, you’ll set up an action to properly receive GIF payload information (your fetch responses). As always, you want to export constants for your action types set to strings of your action types. As a reminder, this is to prevent bugs from mistyping your action types.

export const RECEIVE_GIFS = 'RECEIVE_GIFS';

Now it’s time to write a function that returns your action, an object literal. Write receiveGifs as a function that takes in gifs data as a parameter and returns an action object. The object should have two keys: one for the type and another for the gifs data. Your function should look like the following:

const receiveGifs = gifs => {
  return {
    type: RECEIVE_GIFS,
    gifs
  }
};

Before testing this action creator, you’ll need a reducer.

gifsReducer

Let’s write a switch case and default switch return in your gifsReducer.js file. Note that the gifsReducer function receives the previous state and an action. Recall that a reducer describes how a slice of state should change based on a dispatched action. It should always return the new state without mutating the previous state. If the action dispatched to the reducer should not change the state, the reducer should return the previous state by default. You will need to import the RECEIVE_GIFS constant from your gifActions.js file.

Your reducer should look similar to the this one:

// TODO: Import the `RECEIVE_GIFS` constant

const gifsReducer = (state = [], action) => {
  switch (action.type) {
    // TODO: Return the GIFs from the action object if the action type is `RECEIVE_GIFS`
    // TODO: Return the previous state by default
  }
};

export default gifsReducer;

rootReducer

Recall the state shape you saw earlier in the project instructions. The gifsReducer above should control the gifs slice of the application state. You’ll create and export a rootReducer with Redux’s combineReducers function to assign control of different slices of state to their prospective reducer functions to create the application state structure.

This project only needs one reducer, but using combineReducers would allow you to easily add more state slices in the future.

The combineReducers function has already been imported for you. Take a moment to import your gifsReducer and set the gifs slice of state to its reducer, like so:

gifs: gifsReducer,

Now that you have your reducers set up to structure your application’s global state, you’ll need to set up the Redux store to hold that global state.

Phase 3: The Redux store

The store holds the global state of an application, so you’ll need to create it before you can test your reducer. Remember that Redux provides a createStore function that receives a reducer, optional preloadedState, and an optional enhancer. Begin by writing a configureStore function that passes your rootReducer to createStore.

// store.js
import { createStore } from 'redux';
// TODO: Import middleware
import rootReducer from './reducers/rootReducer';

const configureStore = () => {
  return createStore(rootReducer);
};

export default configureStore;

Import configureStore into your entry index.js file, then use the function to generate the Redux store.

const store = configureStore();

Now you’ll work on providing the store you have generated to your application’s components! Begin by passing the store you’ve generated as a prop to the Root component. Your index.js file should look something like this:

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './components/Root';
import configureStore from './store';
import { fetchGifs } from './util/apiUtil';

window.fetchGifs = fetchGifs;
const store = configureStore();

ReactDOM.render(
  <React.StrictMode>
    <Root store={store} />
  </React.StrictMode>,
  document.getElementById('root')
);

Now go into your Root.js file and import Provider from react-redux. Use the Provider component to set the Redux store through the Provider component’s expected store prop. Remember how you have just configured and passed a store as a prop to the Root component in your entry file (index.js). Use the Root component’s store prop to set the store prop of the Provider:

// Root.js
import React from 'react';
import { Provider } from 'react-redux';
import App from './App';

const Root = ({ store }) => (
  <Provider store={store}>
    <App />
  </Provider>
);

export default Root;

Now that you have configured your Redux store and tested your fetchGifs function, let’s connect the function to the store to implement a full Redux cycle. In the next phase, you’ll define a thunk action creator function and use the thunk middleware so that each fetch response is dispatched as a change to the global application state.

Phase 4: Thunk middleware

Let’s refactor how you fetch GIFs by using a thunk action creator. Recall that we use a thunk action creator to return a function. When that function is called with an argument of dispatch, the function can dispatch additional actions.

Begin by refactoring your configureStore function in the store.js file to incorporate your thunk middleware. Remember that Redux provides thunk middleware from the redux-thunk module. Import the thunk middleware and applyMiddleware function from Redux. You’ll use the applyMiddleware function, with your thunk middleware as an argument, to set the optional enhancer argument in the createStore function:

Your store.js file should now include the following:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/rootReducer';

const configureStore = () => {
  return createStore(rootReducer, applyMiddleware(thunk));
};

export default configureStore;

Now your Redux cycle is almost good to go! To summarize your progress to this point, you have configured your Redux store, provided the store to your application, written a fetch function in a util file, defined an action type, and defined an action creator.

The last step is to define a thunk action creator that will dispatch the receiveGifs action after the Giphy API call is successful!

Begin by importing your API util function into your gifActions.js file. Since you want to easily associate your thunk action creators with a specific action, and you’ll often have more than one function exported from your util file, use a namespace to import your fetch function:

import * as APIUtil from '../util/apiUtil';

You would then invoke your fetchGifs function in your apiUtil.js file like so:

APIUtil.fetchGifs(searchQuery);

Now it’s time to write your thunk action creator! Define and export a function named fetchGifs that receives a search term and returns a function that can be called with dispatch. Your function will use a promise to parse the fetch response to JSON and dispatch the receiveGifs action with the fetch response data after the APIUtil.fetchGifs call is successful.

Your thunk action creator should look like the following:

export const fetchGifs = searchTerm => {
  return dispatch => {
    return APIUtil.fetchGifs(searchTerm)
      .then(res => res.json())
      .then(res => dispatch(receiveGifs(res.data)));
  }
};

Or you can clean up the function by using implicit returns with ES6 arrow functions:

export const fetchGifs = searchTerm => dispatch => (
  APIUtil.fetchGifs(searchTerm)
    .then(res => res.json())
    .then(res => dispatch(receiveGifs(res.data)))
);

Phase 5: Test your thunk action creator

Let’s take a moment to test your the fetchGifs thunk action creator you just defined.

Import fetchGifs from your gifActions.js file to your entry index.js file. You’ll hit the error Parsing error: Identifier 'fetchGifs' has already been declared since have already imported the fetchGifs function from your apiUtil.js file. Note that this is why using a namespace to import * as APIUtil from your apiUtil.js file is important.

Update the import statement from your ./util/apiUtil.js file with a namespace:

import * as APIUtil from './util/apiUtil';

Now you’ll want to put your fetch function, thunk action creator, and Redux store on the window. This way you can view your application’s global state and compare the result of your fetch function and the dispatched thunk action creator:

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Root from './components/Root';
import configureStore from './store';
import * as APIUtil from './util/apiUtil';
import { fetchGifs } from './actions/gifActions';

const store = configureStore();
window.apiFetchGifs = APIUtil.fetchGifs;
window.fetchGifs = fetchGifs;
window.store = store;

ReactDOM.render(
  <React.StrictMode>
    <Root store={store} />
  </React.StrictMode>,
  document.getElementById('root')
);

Now you’ll use the getState() and dispatch(action) store methods to view and update your application’s global state. Try the following code in the browser’s console before continuing to the next phase:

 // Return the initial application state
store.getState();

// Use the thunk action creator to dispatch the fetch response and populate state
store.dispatch(fetchGifs('puppy'));

// Return the application state populated with GIFs
store.getState();

Congratulations! You just wrote a Redux cycle to populate your application’s global state with a response from the Giphy API.

Notice how your application’s global state changed after invoking the dispatch(fetchGifs('puppy)) method. Now you can pass the fetchGifs thunk action creator through a Redux container so that a fetch call can be dispatched from your component to update the global state in the same way!

Phase 6: Logger middleware

Now instead of manually logging the status of your global state and the response of your dispatch call, you can use the Redux logger middleware to automatically do so. Import logger from redux-logger into your store.js file:

import logger from 'redux-logger';

Just like how you applied your thunk middleware to your configured store, you’ll invoke the applyMiddleware() function with your logger middleware. Your updated store.js file should look something like this:

// store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducers/rootReducer';

const configureStore = () => {
  return createStore(rootReducer, applyMiddleware(thunk, logger));
};

export default configureStore;

Now go back to your browser’s console and dispatch a fetch request:

store.dispatch(fetchGifs('puppy'))

Upon a successful fetch, you should have received a helpful log with your application’s prev state, the dispatched action object, and your application’s next state from the logger middleware you just utilized!

Phase 7: Container component

Now that you’ve set up the global state of your application, it’s time to connect your React components to the global state! Remember how you used the Provider component in your Root.js file to pass the configured store as a prop to the Provider component.

Just like how a Context.Provider component expects value as a prop to set the context object, the Redux Provider component expects store as a prop to share the store’s global state with nested components. Instead of rendering your App component as the child of the Provider, you’ll render an AppContainer.

AppContainer

In your AppContainer.js file, you will define a mapStateToProps function and a mapDispatchToProps function and invoke the connect() function from Redux. Invoking the connect() function will connect the App component with the Redux store. Within the container, you will pass slices of state and dispatched thunk action creators as props to the App component.

Note that the skeleton has already imported the connect function from react-redux, your fetchGifs thunk action creator from the gifAction.js file, and the App component.

Take a look at the code skeletons of the mapStateToProps and mapDispatchToProps functions. Think of these functions as functions that take in state or dispatch as arguments to return object literals that represent the props that your App component will receive.

Have your mapStateToProps function return an object with a gifs property set to the state.gifs slice of state. Doing this will pass a gifs prop into your App component.

Next, have your mapDispatchToProps function return an object with a fetchGifs property set to an arrow function that accepts a searchQuery value and dispatches a call to the fetchGifs(searchQuery) function. Remember how you tested the store.dispatch(fetchGifs(searchQuery)) function in the browser console. Think of how you would use an arrow function to almost wait for a searchQuery input from the user before firing the dispatch call.

Lastly, take a moment to examine how your AppContainer.js file is simply exporting the connect() invocation:

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

Now you can render your AppContainer instead of your App component so that the connect() method is invoked to pass a gifs prop and a fetchGifs props to App!

Take a moment to refactor your Root.js file to do so:

// Root.js
import React from 'react';
import { Provider } from 'react-redux';
import AppContainer from './AppContainer';

const Root = ({ store }) => (
  <Provider store={store}>
    <AppContainer />
  </Provider>
);

export default Root;

Phase 8: Presentational components

Now that you’ve created a container to pass (or map) slices of the global state and dispatched actions as props, it’s time to render and update the gifs from the global state!

App

Have your App component take in and destructure your gifs and fetchGifs props. Notice how your component is rendering a SearchBar component and a GifsList component. You’ll want to pass in the fetchGifs function as a prop to the SearchBar component.

Instead of directly passing all of the gifs as a prop to the GifsList component, you’ll refactor your mapStateToProps function and use a selector to map the array of gifs into an array of GIF urls.

Currently, your mapStateToProps function should look something like this:

const mapStateToProps = state => {
  return {
    gifs: state.gifs,
  };
};

Define a getGifUrls selector that takes in the state and uses parameter destructuring to map over each gif in the gifs slice of state to pluck the image URL from the JSON data, like so:

const getGifUrls = ({ gifs }) => (
  gifs.map(gif => gif.images.fixed_height.url)
);

const mapStateToProps = state => {
  return {
    gifUrls: getGifUrls(state),
  };
};

Now you can pass in the gifUrls for your GifsList component to render GIF images without passing unnecessary data as props!

In your SearchBar component, you already have a search form, an inputValue state, and an onChange handler for the form’s input field set up. Right now, your component has several TODO notes to guide your creation of an onSubmit handler for your search form. The onSubmit handler will dispatch the fetchGifs action creator function (don’t forget to prevent the default action of a submit event).

After finishing your SearchBar component, test out your submit event handler and check your logger response to see if your fetchGifs action creator is actually being dispatched to update the application’s global state! Once you see that your fetched gifs have been dispatched to the gifs slice of state, it’s time to render your list of GIFs!

GifsList

Right now, your component has several TODO notes:

Debugging

If you’re having issues rendering a GIF in your project, that means it’s a great time to practice using debugger statements to debug your Redux cycle code! For example, you could set a debugger in the:

If you didn’t hit any issues, congratulations - you have implemented Redux with the Giphy API to create a search API that renders a list of GIFs! Before moving forward to the next project, you should still use debugger statements to step through this project’s Redux cycle.