a React-Redux boilerplate

with Redux-thunk as a middleware

Yogesh Datir
3 min readJun 15, 2020

Steps to add reducers:

  • store.js
  • reducers (rootReducer and others)
  • wrap App.js with Provider
  • add actions and types
  • use redux in components with connect() method.
React redux flow diagram
React Redux Flow Diagram
Folder Structure used in this project

Detail Guide to create a React-Redux boiler-plate:

  1. Install redux and necessary libraries.
    npm i redux react-redux redux-thunk redux-devtools-extension
  • React Redux is the official React binding for Redux. It lets your React components read data from a Redux store, and dispatch actions to the store to update data.
  • Redux Thunk: With a plain basic Redux store, you can only do simple synchronous updates by dispatching an action. Middleware extends the store’s abilities, and let you write async logic that interacts with the store.
  • Redux Devtools Extension: to enable easy debugging and time travel.

2. Create src/store.js.

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

const initialState = {};

const middleware = [thunk];

const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware)),
);

export default store;

3. Create reducers/index.js.

import { combineReducers } from 'redux';export default combineReducers({});

4. Wrap all root component — App with Provider.

import React from 'react';import { Provider } from 'react-redux';
import store from '../store';
function App() {
return (
<Provider store={store}>
<>This is App Component</>
</Provider>
);
}
export default App;

5. Create actions/types.js.

export const FETCH_POSTS = 'FETCH_POSTS';
export const NEW_POST = 'NEW_POST';

6. Create actions/clientActions.js.

import { FETCH_POSTS, NEW_POST } from './types';export const fetchPosts = () => dispatch => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(posts =>
dispatch({
type: FETCH_POSTS,
payload: posts
})
);
};
export const createPost = postData => dispatch => {
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(postData)
})
.then(res => res.json())
.then(post =>
dispatch({
type: NEW_POST,
payload: post
})
);
};

7. Create a reducer ./reducers/clientReducer.js.

import { FETCH_POSTS, NEW_POST } from '../actions/types';const initialState = {
items: [],
item: {}
};
export default function(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
items: action.payload
};
case NEW_POST:
return {
...state,
item: action.payload
};
default:
return state;
}
}

8. Add the reducer entry in the root reducer ./reducers/index.js.

import { combineReducers } from 'redux';
import clientReducer from './clientReducer';
export default combineReducers({
posts: clientReducer
});

9. Use redux in components.

src/components/posts/Posts.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchPosts } from '../../actions/clientActions';
class Posts extends Component {
componentWillMount() {
this.props.fetchPosts();
}
componentWillReceiveProps(nextProps) {
if (nextProps.newPost) {
this.props.posts.unshift(nextProps.newPost);
}
}
render() {
const postItems = this.props.posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
));
return (
<div>
<h1>Posts</h1>
{postItems}
</div>
);
}
}
Posts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.array.isRequired,
newPost: PropTypes.object
};
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
});
export default connect(mapStateToProps, { fetchPosts })(Posts);

src/components/PostForm.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createPost } from '../../actions/clientActions';
class PostForm extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
body: ''
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
onSubmit(e) {
e.preventDefault();
const post = {
title: this.state.title,
body: this.state.body
};
this.props.createPost(post);
}
render() {
return (
<div>
<h1>Add Post</h1>
<form onSubmit={this.onSubmit}>
<div>
<label>Title: </label>
<br />
<input
type="text"
name="title"
onChange={this.onChange}
value={this.state.title}
/>
</div>
<br />
<div>
<label>Body: </label>
<br />
<textarea
name="body"
onChange={this.onChange}
value={this.state.body}
/>
</div>
<br />
<button type="submit">Submit</button>
</form>
</div>
);
}
}
PostForm.propTypes = {
createPost: PropTypes.func.isRequired
};
export default connect(null, { createPost })(PostForm);

Update the App.js

import React from 'react';import Posts from './posts/Posts';
import PostForm from './posts/PostForm';
import { Provider } from 'react-redux';
import store from '../store';
function App() {
return (
<Provider store={store}>
<div>
<PostForm />
<hr />
<Posts />
</div>
</Provider>
);
}
export default App;

--

--

Yogesh Datir

React Developer at Entrata India, Interested in Personal Finance, Quilling, Geopolitics and economies.