We have created a React Application with React Router v4, then we also learned how to use Redux to manage state in Redux combineReducers example and Redux Reducer example – filter & sort data. In this tutorial, we’re gonna combine all of them by connecting React with Redux using react-redux
.
Related Posts:
– How to filter list with input text – react-redux example
– How to sort list – react-redux example
– Add Item Form – react-redux example
– How to test React Redux application with Jest
More Practice:
– React Redux – Firebase CRUD Operations example
– React Redux Firebase Authentication – Google Account Sign in/Sign out example
React Redux
connect() function
react-redux
is a Redux binding for React that allows us connect React with Redux in an efficient way.
The most important function is connect()
that:
– connects a React Component with a Redux Store.
– returns a new connected Component class without modifying the Component class passed to it.
This is how we use connect()
function:
... import { connect } from 'react-redux'; const BookList = (props) => (Use {props.books}); const mapStateToProps = (state) => { return { books: getVisibleBooks(state.books, state.filters) }; } export default connect(mapStateToProps)(BookList);
-> mapStateToProps
get books
props from state.
-> connect()
function get the function as parameter and apply to BookList
Component to return a new connected React Component that can work with React state.
We can use connect()
with one or more arguments depending on the use case:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
Please visit React Redux API – connect for details.
We have 2 important arguments:
– mapStateToProps(state)
function connects a part of Redux state to React Component props.
The returned props of this function must be a plain object, which will be merged into the connected Component’s props, now it will have access to the exact part of Redux store.
– mapDispatchToProps(dispatch)
function is similar to mapStateToProps
, but for actions. It connects Redux actions to React props. Therefore connected React Component can dispatch Redux actions.
Provider
In the example above, we use mapStateToProps
to connect Redux state to React BookList
Component props. But it’s not enough.
=> We have to do one more thing: make the Redux store available to the connect() call in the Component hierarchy below. We will wrap a parent or ancestor Component in Provider
.
Provider
is an high order component that wraps up React application and makes it aware of the entire Redux store. That is, it provides the store to its child components.
So if we want our entire React App to access the store, just put the App Component within Provider
.
// app.js import { Provider } from 'react-redux'; const template = (); // AppRouter.js const AppRouter = () => ( ... ); // DashBoard.js const DashBoard = () => ( );
Once we connected Redux state to React Component props, every time state is updated, props changes.
Example Overview
Goal
We will build a React Application using React Router v4 and Redux. It can get App state data and display them in a Component:
Project Structure
We have 2 main parts:
1- Redux
With our desired state like:
const demoState = { books: [ { id: '123abcdefghiklmn', title: 'Origin', description: 'Origin thrusts Robert Langdon into the dangerous intersection of humankind’s two most enduring questions.', author: 'Dan Brown', published: 2017 } ], filters: { text: 'ori', sortBy: 'published', // published or title startYear: undefined, endYear: undefined } };
We will have folder tree like this:
– Redux Actions and Reducers are separated in actions and reducers.
– stores folder contains Redux Store Component which is created from result of combineReducers(books, filters)
.
– selectors/book.js exports a functions that returns list of Book items after filtering and sorting.
2- React Components
The folder structure is just like React project that we have learned before:
– AppRouter for implementing React Router.
– DashBoard, AddBook, EditBook, Help, NotFound are React Components that will work with Router.
– BookList Component is a child Component of DashBoard. In this Component, we use react-redux connect()
function. Then props that is gotten from state will be used in Book Component – child of BookList Component.
– We will use react-redux Provider
inside app.js.
3- React Redux App Structure
Our project structure is combination of 2 folder structure above:
Practice
Setup environment
Install Packages
– Open package.json:
{ ... "author": "JavaSampleApproach", "scripts": { "serve": "live-server public", "build": "webpack", "dev-server": "webpack-dev-server" }, "dependencies": { "babel-cli": "6.24.1", "babel-core": "6.25.0", "babel-loader": "7.1.4", "babel-preset-env": "1.6.1", "babel-preset-react": "6.24.1", "babel-plugin-transform-object-rest-spread": "6.26.0", "react": "16.3.0", "react-dom": "16.3.0", "react-modal": "3.3.2", "react-router-dom": "4.2.2", "webpack": "4.4.1", "webpack-cli": "2.0.13", "webpack-dev-server": "3.1.1", "style-loader": "0.20.3", "css-loader": "0.28.11", "sass-loader": "6.0.7", "node-sass": "4.8.3", "redux": "3.7.2", "uuid": "3.2.1", "react-redux": "5.0.7" } }
Run cmd yarn install
.
– Add plugin to .babelrc
:
{ ... "plugins": [ "transform-object-rest-spread" ] }
Configure Webpack
Open webpack.config.js:
const path = require('path'); module.exports = { ... module: { rules: [...] }, devtool: 'cheap-module-eval-source-map', devServer: { contentBase: path.join(__dirname, 'public'), historyApiFallback: true } };
historyApiFallback
option is specifically for webpack-dev-server. Setting it true
will effectively ask the server to fallback to index.html when a requested resource cannot be found (404 occurs).
Redux
Create Redux Actions
actions/books.js:
import uuid from 'uuid'; export const addBook = ({ title = '', description = '', author = '', published = 0 } = {}) => ({ type: 'ADD_BOOK', book: { id: uuid(), title, description, author, published } }); export const removeBook = ({ id } = {}) => ({ type: 'REMOVE_BOOK', id });
actions/filters.js:
export const filterText = (text = '') => ({ type: 'FILTER_TEXT', text }); export const startYear = (startYear) => ({ type: 'START_YEAR', startYear }); export const endYear = (endYear) => ({ type: 'END_YEAR', endYear }); export const sortBy = (sortType) => ({ type: 'SORT_BY', sortType }); const filtersReducerDefaultState = { text: '', sortBy: '', startYear: undefined, endYear: undefined }; export const clear = () => ({ type: 'CLEAR', defaultFilter: filtersReducerDefaultState });
Create Redux Reducers
reducers/books.js:
const booksReducerDefaultState = []; export default (state = booksReducerDefaultState, action) => { switch (action.type) { case 'ADD_BOOK': return [ ...state, action.book ]; case 'REMOVE_BOOK': return state.filter(({ id }) => id !== action.id); default: return state; } };
reducers/filters.js:
const filtersReducerDefaultState = { text: '', sortBy: '', startYear: undefined, endYear: undefined }; export default (state = filtersReducerDefaultState, action) => { switch (action.type) { case 'FILTER_TEXT': return { ...state, text: action.text }; case 'START_YEAR': return { ...state, startYear: action.startYear }; case 'END_YEAR': return { ...state, endYear: action.endYear }; case 'SORT_BY': return { ...state, sortBy: action.sortType }; case 'CLEAR': return { ...state, text: action.defaultFilter.text, sortBy: action.defaultFilter.sortBy, startYear: action.defaultFilter.startYear, endYear: action.defaultFilter.endYear }; default: return state; } }
Create Redux Store
store/store.js:
import { createStore, combineReducers } from "redux"; import booksReducer from '../reducers/books'; import filtersReducer from '../reducers/filters'; const demoState = { books: [ { id: '123abcdefghiklmn', title: 'Origin', description: 'Origin thrusts Robert Langdon into the dangerous intersection of humankind’s two most enduring questions.', author: 'Dan Brown', published: 2017 } ], filters: { text: 'ori', sortBy: 'published', // published or title startYear: undefined, endYear: undefined } }; export default () => { return createStore( combineReducers({ books: booksReducer, filters: filtersReducer } )); };
Create Selector for filtering and sorting
selectors/books.js:
// getVisibleBooks export default (books, { text, sortBy, startYear, endYear }) => { return books.filter(book => { const textMatch = book.title.toLowerCase().includes(text.toLowerCase()) || book.description.toLowerCase().includes(text.toLowerCase()); const startYearMatch = typeof startYear !== 'number' || startYear <= book.published; const endYearMatch = typeof endYear !== 'number' || book.published <= endYear; return textMatch && startYearMatch && endYearMatch; }).sort((book1, book2) => { if (sortBy === 'title') { return book1.title.localeCompare(book2.title); } else if (sortBy === 'published') { return book1.published < book2.published ? -1 : 1; } }); }
React
Create Components
- For each Page that displays when clicking on Navigation item, we add one Component. So we have 4 Components: Dashboard, AddBook, EditBook, Help.
- We need a Component for 404 status, it's called NotFound
.
- Now, we put a group of NavLink
in header of Header
Component:
Header.js
import React from 'react'; import { NavLink } from 'react-router-dom'; const Header = () => (); export default Header; Java Sample Approach
Book Mangement Application
Dashboard Add Book Help
Connect React Components with Redux
- Dashboard has BookList as a child:
DashBoard.js
import React from 'react'; import BookList from './BookList'; const DashBoard = () => (); export default DashBoard;
- In BookList Component, we use react-redux connect()
function:
BookList.js
import React from 'react'; import { connect } from 'react-redux'; import Book from './Book'; import getVisibleBooks from '../selectors/books'; const BookList = (props) => (Book List:); const mapStateToProps = (state) => { return { books: getVisibleBooks(state.books, state.filters) }; } export default connect(mapStateToProps)(BookList);{props.books.map(book => { return (
- ); })}
- props that is gotten from state will be used in Book Component - child of BookList Component:
Book.js
import React from 'react'; const Book = ({ title, description, author, published }) => (); export default Book;{title} ({published})
Author: {author}
Create Router
Inside routers/AppRouter.js:
import React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import Header from '../components/Header'; import DashBoard from '../components/DashBoard'; import AddBook from '../components/AddBook'; import EditBook from '../components/EditBook'; import Help from '../components/Help'; import NotFound from '../components/NotFound'; const AppRouter = () => (); export default AppRouter;
Run and Check Result
app.js
- get store.
- dispatch addBook
action after every second (we want to check if Component props is updated whenever state changes).
- use react-redux Provider
to make Redux store available for connecting with React Components.
import React from 'react'; import ReactDOM from 'react-dom'; import AppRouter from './routers/AppRouter'; import getAppStore from './store/store'; import { addBook } from './actions/books'; import { filterText, startYear, endYear, sortBy, clear } from './actions/filters'; import getVisibleBooks from './selectors/books'; import './styles/styles.scss'; import { Provider } from 'react-redux'; const store = getAppStore(); setTimeout(() => { store.dispatch(addBook({ title: 'Origin', description: 'Origin thrusts Robert Langdon into the dangerous intersection of humankind’s two most enduring questions.', author: 'Dan Brown', published: 2017 })) }, 1000); setTimeout(() => { store.dispatch(addBook({ title: 'Harry Potter and the Deathly Hallows', description: 'The seventh and final novel of the Harry Potter series.', author: 'J. K. Rowling', published: 2007 })) }, 2000); setTimeout(() => { store.dispatch(addBook({ title: 'The 100-Year-Old Man Who Climbed Out the Window and Disappeared', author: 'Jonas Jonasson', published: 2009 })) }, 3000); const template = (); ReactDOM.render(template, document.getElementById('app'));
Check Result
Source Code
For running:
- yarn install
- yarn run dev-server
or yarn run build
, then yarn run serve
.
More Practice
Add remove Button for each Item
Using react-redux connect()
, we can call Redux dispatch()
function directly:
Book.js
import React from 'react'; import { connect } from 'react-redux'; import { removeBook } from '../actions/books'; const Book = ({ id, title, description, author, published, dispatch }) => (); export default connect()(Book);{title} ({published})
Author: {author}
Source Code
Last updated on August 23, 2018.
Thank you very much. I saw alot of examples that didn’t even work. This works out of the box. And it uses Yarn, Scss and React-Router-v4!
This example runs on which port???
this would be much better without sass – which always fails to compile it’s native bindings for me.
“`
Downloading binary from https://github.com/sass/node-sass/releases/download/v4.8.3/darwin-x64-64_binding.node
Cannot download “https://github.com/sass/node-sass/releases/download/v4.8.3/darwin-x64-64_binding.node”:
HTTP error 404 Not Found
Hint: If github.com is not accessible in your location
try setting a proxy via HTTP_PROXY, e.g.
export HTTP_PROXY=http://example.com:1234
or configure npm proxy via
npm config set proxy http://example.com:8080
“`
followed by a whole bunch of make errors.