Redux: Thunk
We are going to do simple blog app. We will fetch data from jsonplaceholder.typicode.com
.
What is Redux Thunk ?
- redux = redux library
- react-redux = integration layer between react and redux
- axios = help us to make network requests
- redux-thunk = middleware to help us make requests in a redux applications
src/apis/jsonPlaceholder.js
import axios from 'axios';
export default axios.create({
baseURL: 'https://jsonplaceholder.typicode.com'
});
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import App from './components/App';
import reducers from './reducers';
const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector('#root ')
);
src/actions/index.js
import _ from 'lodash'; // we need to install lodash library to use memoize functions, so we will get data for each user only once
import jsonPlaceholder from '../apis/jsonPlaceholder';
/*
//this will not work because we want to use async await
//this code is transpiled by babel to es2015
//and the it doesnt return plain object as it is required in redux actions
//you can check at babeljs.io
export const fetchPosts = async () => {
const response = await jsonPlaceholder.get('/posts');
return (
type: "FETCH_POSTS",
payload: response
);
}
*/
export const fetchPostsAndUsers = () => async (dispatch, getState) => {
//if we are calling action creator from action creator, we need to use dispatch
console.log('about to fetch posts');
await dispatch(fetchPosts());
console.log('posts fetched !');
console.log(getState().posts);
//we will iterate through list of posts and get only unique user ids. _ is lodash library
//otherwise it will call request to user api each time blog post is loaded
const userIds = _uniq(_.map(getState().posts, 'userId'));
userIds.forEach(id => dispatch(fetchUser(id)));
//alternative syntax
/*
_.chain(getState().posts)
.map('userId')
.uniq()
.forEach(id => dispatch(fetchUser(id)))
.value();
*/
}
//we will use redux-thunk
export const fetchPosts = async () => {
return async (dispatch) => {
const response = await jsonPlaceholder.get('/posts');
dispatch({ type: 'FETCH_POSTS', payload: response.data});
};
};
//this is the same like export above, just shorter syntax
export const fetchUser = id => async dispatch => {
const response = await jsonPlaceholder.get(`/users/{$id}` );
dispatch({ type: 'FETCH_USER', payload: response.data });
}
src/components/App.js
import React from 'react';
import PostList from './PostList';
const App = () => {
return (
<div className="ui container">
<PostList />
</div>
);
}
export default App;
src/components/PostList.js
import React from 'react';
import { connect } from 'react-redux';
import { fetchPostsAndUsers } from '../actions';
import UserHeader from './UserHeader';
class PostList extends React.Component {
componentDidMount() {
this.props.fetchPostsAndUsers();
}
renderList() {
return this.props.posts.map(post => {
return (
<div className="item" key={post.id}>
<div className="content">
<div className="description">
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
<UserHeader userId={post.userId} />
</div>
</div>
);
});
}
render() {
return <div className="ui relaxed divided">{this.renderList()}</div>;
}
}
const mapStateToProps = (state) => {
return {
posts: state.posts
};
}
//there was null at place of mapStateToProps, because we didnt have mapStateToProps function before
export default connect(mapStateToProps, { fetchPostsAndUsers })(PostList);
br />
src/components/UserHeader.js
import React from 'react';
import { connect } from 'react-redux';
class UserHeader extends React.Component {
render() {
const { user } = this.props;
if (!user) {
return null;
}
return <div className="header">{user.name}</div>;
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find(user => user.id === ownProps.userId ) }
}
//there was null at place of mapStateToProps, because we didnt have mapStateToProps function before
export default connect(mapStateToProps)(UserHeader);
src/reducers/postsReducer.js
export default (state = [], action) => {
switch (action.type) {
case 'FETCH_POSTS':
return action.payload;
default:
return state;
}
}
src/reducers/usersReducer.js
export default (state = [], action) => {
switch (action.type) {
case 'FETCH_USER':
return [...state, action.payload];
default:
return state;
}
}
src/reducers/index.js
import { combineReducers } from 'redux';
import postReducer from './postsReducer';
import usersReducer from './usersReducer';
export default combineReducers({
posts: postsReducer,
users: usersReducer
});