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
});