In this project, you’ll connect Redux to a React application. You'll configure a Redux store, create Redux reducers and action creators, and use them in a Grocery Store application to add produce to a shopping cart. You'll also learn how to normalize data in a Redux store.
When you finish today's project, your application should have the following features:
- Display all the produce in a list
- Add produce to a cart
- Increment/decrement the quantity of produce in the cart
- Checkout the cart
Clone the starter repository from the Download
link below.
Run npm install
and npm start
to start the development server.
Navigate to http://localhost:3000. You should see the text "Grocery Store". If you click on the "Checkout" button in the navigation bar, it should open a side menu. Since there is no produce listed, you cannot add items to the shopping cart yet.
The Cart
, ProduceList
, and ProduceDetails
are the components you need to
connect to the Redux store. Take a look at the code there to familiarize
yourself with it.
src/mockData/produce.json provides the mock data that you will use to populate the Redux store. The file has a JSON array of objects. Each object has the following structure:
{
id: ID,
name: String,
liked: Boolean
}
In your preview of the code, make sure to understand how all the components connect (i.e., the parent-child relationships of all the components, the component tree).
npm install
the following dependencies:
redux
- the Redux packagereact-redux
- for connecting Redux to React
npm install -D
the following dev-dependencies:
redux-logger
- a debugging tool for logging all actions dispatched
First thing you need to do is set up and configure the Redux store.
Create a folder called store inside of src. Inside of that folder, create a file called index.js. In this file, you will define the root reducer and a function that will return a Redux store.
Import createStore
, combineReducers
, applyMiddleware
, and compose
from
redux.
// ./src/store/index.js
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
createStore
- creates a Redux storecombineReducers
- creates one reducer from multiple reducers as slices of stateapplyMiddleware
- a store enhancer that will allow you to attach middlewares (a middleware is a function called before any action hits the root reducer)compose
- another store enhancer that will allow you to use more than one store enhancer
Next, define the rootReducer
which will be the root reducer for the Redux
store. The rootReducer
will just be the return of combineReducers
invoked
with an empty object for now.
// ./src/store/index.js
// ...
const rootReducer = combineReducers({
});
Now, you are going to create a store enhancer that will be set only when your
application is in development. When in development, you want to add the
redux-logger
middleware and the Redux dev tools extension store enhancer to
your store. For redux-logger
, you will use applyMiddleware
, which returns
a store enhancer. The store enhancer for the Redux dev tools extension is set
by the extension to a global property,
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
. This store enhancer combines
other store enhancers into one, just like compose
from redux
does.
Set the enhancer
variable when in development to use these enhancers like so:
// ./src/store/index.js
// ...
let enhancer;
if (process.env.NODE_ENV !== "production") {
const logger = require("redux-logger").default;
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
enhancer = composeEnhancers(applyMiddleware(logger));
}
Let's break this down.
The logger
variable is set to the default
export of the imported
redux-logger
package using require
instead of ES6 module imports. Why? ES6
module imports must be defined at the very top of the file in the outermost
scope and will be loaded in all node environments. However, redux-logger
is a
development dependency and cannot be loaded in production. Loading it with
require
is needed if you only want it in a certain node environment.
The composeEnhancers
variable is set to the Redux dev tools extension's
store enhancer, window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
or the compose
function from redux
if the extension fails to load or if you don't have the
extension installed in your browser.
The Redux compose
function takes in store enhancers as its arguments and
combines them to create a single store enhancer. The only other store enhancer
that you need is the applyMiddleware
invoked with the loaded logger
.
enhancer
is set to the combined store enhancer.
Finally, define a function called configureStore
that will take in a
preloadedState
and return the result of createStore
invoked with the
rootReducer
, the preloadedState
, and the enhancer
. Export the
configureStore
function as the default export.
// ./src/store/index.js
// ...
const configureStore = (preloadedState) => {
return createStore(rootReducer, preloadedState, enhancer);
};
export default configureStore;
Now, you need to wrap the React application with the Redux store provider.
Import the configureStore
function into the entry file, src/index.js.
Import the Provider
component from react-redux
.
Initialize a variable called store
and set it to the return of
configureStore
(a preloadedState
does not need to be passed in, it can be
undefined
).
In the Root
function component, wrap the BrowserRouter
component with the
Provider
component. Pass the prop of store
with the value of store
into
the Provider
.
Your entry file should look like this:
// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import configureStore from './store';
import './index.css';
import App from './App';
const store = configureStore();
function Root() {
return (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
}
ReactDOM.render(
<React.StrictMode>
<Root />
</React.StrictMode>,
document.getElementById('root')
);
Let's test your setup!
Navigate to http://localhost:3000 and open up the browser's dev tools. In the
console, you should see a message about an invalid reducer. That's because the
rootReducer
isn't combining any reducers yet. Ignore this warning for now.
Open the "Redux" tab in your browser's dev tools. You should not see the message "No store found".
In your entry file, expose the store
variable on the window
only in
development for testing purposes.
// ./src/index.js
// ...
if (process.env.NODE_ENV !== "production") {
window.store = store;
}
Navigate to http://localhost:3000 and refresh. You should see the store
on
the window
in your browser's dev tools console. If you call store.getState()
then you should just get back an empty object.
If you are having issues while testing, check your syntax in your store and entry files.
Commit your setup code!