How to connect React with Redux – react-redux example

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) => (
    <div>
        Use {props.books}
    </div>
);

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 = (
    <Provider store={store}>
        <AppRouter />
    </Provider>
);

// AppRouter.js
const AppRouter = () => (
                ...
                <Route path="/" component={DashBoard} exact={true} />
                <Route ... />
);

// DashBoard.js
const DashBoard = () => (
    <div>
        <BookList/>
    </div>
);

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:

react-redux-example-goal

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:

react-redux-example-organize-redux-structure

– 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:

react-redux-example-React-project-structure

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:

react-redux-example-project-structure

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;
    }
};
</pre>
<strong>reducers/filters.js</strong>:
<pre class="lang:java">
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 = () => (
    <header>
        <h2>Java Sample Approach</h2>
        <h4>Book Mangement Application</h4>
        <div className='header__nav'>
            <NavLink to='/' activeClassName='activeNav' exact={true}>Dashboard</NavLink>
            <NavLink to='/add' activeClassName='activeNav'>Add Book</NavLink>
            <NavLink to='/help' activeClassName='activeNav'>Help</NavLink>
        </div>
    </header>
);

export default Header;

Connect React Components with Redux

Dashboard has BookList as a child:
DashBoard.js


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

const DashBoard = () => (
    <div className='container__list'>
        <BookList/>
    </div>
);

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) => (
    <div>
        Book List:
        <ul>
            {props.books.map(book => {
                return (
                    <li key={book.id}>
                        <Book {...book} />
                    </li>
                );
            })}
        </ul>
    </div>
);

const mapStateToProps = (state) => {
    return {
        books: getVisibleBooks(state.books, state.filters)
    };
}

export default connect(mapStateToProps)(BookList);

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 }) => (
    <div>
        <h4>{title} ({published})</h4>
        <p>Author: {author}</p>
    </div>
);

export default Book;

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 = () => (
    <BrowserRouter>
        <div className='container'>
            <Header />
            <Switch>
                <Route path="/" component={DashBoard} exact={true} />
                <Route path="/add" component={AddBook} />
                <Route path="/book/:id" component={EditBook} />
                <Route path="/help" component={Help} />
                <Route component={NotFound} />
            </Switch>
        </div>
    </BrowserRouter>
);

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 = (
    <Provider store={store}>
        <AppRouter />
    </Provider>
);

ReactDOM.render(template, document.getElementById('app'));

Check Result

react-redux-example-result

Source Code

ReactRedux

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 }) => (
    <div>
        <h4>{title} ({published})</h4>
        <p>Author: {author}</p>
        <button onClick={() => {
            dispatch(removeBook({ id }));
        }}>Remove</button>
    </div>
);

export default connect()(Book);

Source Code

ReactRedux-addRemoveButton

45 thoughts on “How to connect React with Redux – react-redux example”

  1. 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!

  2. 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.

  3. Hello,

    My name is Johan, I am a PHP programmer that specializes in data driven web applications.

    Anything related to PHP, MySQL, Data scraping etc.

    If you have any custom jobs you can add me on skype to discuss your requirements.

    Skype: cmsdevelopers

    Regards,
    Johan

  4. 976995 804651Our own chaga mushroom comes with a schokohutige, consistent, charcoal-like arrival, a whole lot of dissimilar towards the style of the normal mushroom. Chaga Tincture 35040

  5. Hello, you used to write fantastic, but the last few posts have been kinda boring… I miss your super writings. Past several posts are just a little bit out of track! come on!”To be content with what one has is the greatest and truest of riches.” by Cicero.

  6. I’m truly enjoying the design and layout of your site. It’s a very easy on the eyes which makes it much more pleasant for me to come here and visit more often. Did you hire out a designer to create your theme? Superb work!

  7. Hi there, just became aware of your blog through Google, and found that it is really informative. I am gonna watch out for brussels. I’ll be grateful if you continue this in future. Many people will be benefited from your writing. Cheers!

  8. The when I just read a weblog, I really hope who’s doesnt disappoint me around this place. I mean, I know it was my replacement for read, but I actually thought youd have something fascinating to mention. All I hear can be a few whining about something that you could fix when you werent too busy looking for attention.

  9. I’ve been exploring for a bit for any high quality articles or blog posts on this sort of area . Exploring in Yahoo I at last stumbled upon this website. Reading this information So i’m happy to convey that I’ve a very good uncanny feeling I discovered just what I needed. I most certainly will make certain to don’t forget this website and give it a look regularly.

  10. What i do not understood is actually how you are not actually much more well-liked than you may be right now. You are very intelligent. You realize thus significantly relating to this subject, produced me personally consider it from a lot of varied angles. Its like men and women aren’t fascinated unless it is one thing to accomplish with Lady gaga! Your own stuffs outstanding. Always maintain it up!

  11. Excellent web site. A lot of helpful information here. I?¦m sending it to a few pals ans additionally sharing in delicious. And certainly, thanks in your effort!

  12. I love your blog.. very nice colors & theme. Did you make this website yourself or did you hire someone to do it for you? Plz reply as I’m looking to create my own blog and would like to know where u got this from. cheers

  13. Hello, Neat post. There is an issue along with your website in web explorer, might test thisK IE still is the marketplace chief and a huge component of people will pass over your magnificent writing because of this problem.

  14. With almost everything which appears to be building throughout this specific subject matter, your perspectives tend to be very stimulating. On the other hand, I am sorry, but I can not give credence to your entire suggestion, all be it stimulating none the less. It appears to us that your commentary are actually not totally rationalized and in actuality you are your self not even completely confident of the point. In any case I did appreciate reading through it.

  15. I genuinely enjoy reading on this web site, it has got wonderful posts. “The longing to produce great inspirations didn’t produce anything but more longing.” by Sophie Kerr.

  16. I would like to thnkx for the efforts you have put in writing this blog. I am hoping the same high-grade blog post from you in the upcoming as well. In fact your creative writing abilities has inspired me to get my own blog now. Really the blogging is spreading its wings quickly. Your write up is a good example of it.

  17. I?¦ve read some good stuff here. Certainly value bookmarking for revisiting. I surprise how so much effort you place to make this sort of great informative web site.

  18. A lot of thanks for each of your work on this web page. My aunt loves managing internet research and it’s easy to understand why. Most of us notice all concerning the powerful mode you make both useful and interesting tricks by means of your web blog and in addition cause contribution from other individuals on this issue so our own child is without question being taught a great deal. Enjoy the rest of the new year. You’re performing a pretty cool job.

  19. Youre so cool! I dont suppose Ive read anything like this before. So good to find somebody with some unique ideas on this subject. realy thanks for starting this up. this web site is something that’s wanted on the net, someone with a bit originality. useful job for bringing something new to the web!

  20. Get (nearly) every feature you want or need on your site for $500
    Don’t pay the average price of $3,000 or more
    Plus, I can build a brand-new WordPress website in 1 to 2 days
    Also: any edits to your site that are less than 15 minutes are free

    Call or Text 210-493-6193 Today and Save

    David Schipper, CEO
    AdventDigital.net
    Est. 1998
    5 Stars on Google
    BBB A+

Leave a Reply

Your email address will not be published. Required fields are marked *