Dev

By Carlos Santana on
Reading time: 5 minutes

ReduxBlog-0Uvud.png

In this post, I'll show you how to create a small blog using Redux, but there are some concepts that you need to understand first.

Redux Store

The Redux store holds the entire state of your application, and the only way to change the state inside is by dispatching a Redux action. The store is not a class, it is just an object with a few methods on it: getState, dispatch, subscribe and replaceReducer.

React Redux

For this post, we will use the code we created in the article: Implementing Node.js and React using Webpack 4. That means you will need to clone this repository: https://github.com/D3vEducation/node-with-react.

In order to work with Redux, you have to install the following packages: 

npm install redux react-redux

After this, the first thing we need to create is our configureStore file: 

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

// Root Reducer
import rootReducer from './reducers';

export default function configureStore(initialState) {
  const middleware = [
    thunk
  ];

  return createStore(
    rootReducer,
    initialState,
    composeWithDevTools(applyMiddleware(...middleware))
  );
}
File: src/shared/redux/configureStore.js

Once you created the Redux store we need to add our initial state into our html.js, for now, we can create a device state to check if the user's device is mobile (iPhone or Android) or not.

// Environment
const isProduction = process.env.NODE_ENV === 'production';

export default function html({ title }) {
  let path = '/';
  let link = '';

  if (isProduction) {
    path = '/app/';
    link = `<link rel="stylesheet" href="${path}css/main.css" />`;
  }

  return `
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8"> 
        <title>${title}</title> 
        ${link}
      </head>

      <body>
        <div id="root"></div>

        <script>
          // Detecting the user device
          const isMobile = /iPhone|Android/i.test(navigator.userAgent);

          // Creating our initialState
          const initialState = {
            device: {
              isMobile
            }
          };

          // Saving our initialState to the window object
          window.initialState = initialState;
        </script>
        <script src="${path}vendor.js"></script>
        <script src="${path}main.js"></script>
      </body>
    </html>
  `;
}
File: src/backend/render/html.js

Now let's create our device reducer to be able to retrieve the value coming from our initial state:

export default function deviceReducer(state = {}) {
  return state;
}
File: src/shared/reducers/deviceReducer.js

All the reducers need to be combined into a rootReducer, for this, we need to create this file:

// Dependencies
import { combineReducers } from 'redux';

// Shared Reducers
import device from './deviceReducer';

// App Reducers
import blog from '../../../frontend/app/blog/reducer';

const rootReducer = combineReducers({
  device,
  blog
});

export default rootReducer;
File: src/shared/reducers/index.js

In the src/index.js we need to create our Redux store and then add the <Provider> wrapper to be able to connect the application to Redux. 

// Dependencies
import React from 'react';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { Provider } from 'react-redux';

// Components
import App from './frontend/App';

// Redux Store
import configureStore from './shared/redux/configureStore';

// Configuring Redux Store
const store = configureStore(window.initialState);

// Root element
const rootElement = document.querySelector('#root');

// App Wrapper
const renderApp = Component => {
  render(
    <Provider store={store}>
      <AppContainer>
        <Component />
      </AppContainer>
    </Provider>,
    rootElement
  );
};

// Rendering app
renderApp(App);

// Hot Module Replacement
if (module.hot) {
  module.hot.accept('./frontend/App', () => {
    renderApp(require('./frontend/App').default);
  });
}
File: src/index.js

Now let's create our blog app, the first file is for our actionTypes. 

// Posts actions
export const FETCH_POSTS_REQUEST = 'FETCH_POSTS_REQUEST';
export const FETCH_POSTS_RESPONSE = 'FETCH_POSTS_RESPONSE';

// Post actions
export const FETCH_POST_REQUEST = 'FETCH_POST_REQUEST';
export const FETCH_POST_RESPONSE = 'FETCH_POST_RESPONSE';
File: src/frontend/app/blog/actions/actionTypes.js

Then in our actions file, we need to create 2 actions: fetchPosts and fetchPost and for each one, we need to create one for the request action and the other for the response.

// API
import Api from '../api';

// Action Types
import {
  FETCH_POSTS_REQUEST,
  FETCH_POSTS_RESPONSE,
  FETCH_POST_REQUEST,
  FETCH_POST_RESPONSE
} from './actionTypes';

// fetchPosts actions
export const fetchPostsRequest = () => dispatch => {
  dispatch({
    type: FETCH_POSTS_REQUEST
  });
};

export const fetchPostsResponse = payload => dispatch => {
  dispatch({
    type: FETCH_POSTS_RESPONSE,
    payload
  });
};

export const fetchPosts = () => dispatch => {
  dispatch(fetchPostsRequest());

  Api.fetchPosts()
    .then(response => dispatch(fetchPostsResponse(response.data)));
};

// fetchPost actions
export const fetchPostRequest = () => dispatch => {
  dispatch({
    type: FETCH_POST_REQUEST
  });
};

export const fetchPostResponse = payload => dispatch => {
  dispatch({
    type: FETCH_POST_RESPONSE,
    payload
  });
};

export const fetchPost = id => dispatch => {
  dispatch(fetchPostRequest());

  Api.fetchPost(id)
    .then(response => dispatch(fetchPostResponse(response.data)));
};
File: src/frontend/app/blog/actions/index.js

As you can see I have an API file where the service requests are actually happening, I like to separate this in order to be cleaner and easy to maintain, for this blog we will use the service offered from https://jsonplaceholder.typicode.com

// Dependencies
import axios from 'axios';

// Endpoints
const API_POSTS = 'https://jsonplaceholder.typicode.com/posts';
const API_POST = 'https://jsonplaceholder.typicode.com/posts/';

class Api {
  static fetchPosts() {
    return axios.get(API_POSTS).then(response => response);
  }

  static fetchPost(id) {
    return axios.get(API_POST + id).then(response => response);
  }
}

export default Api;
File: src/frontend/app/blog/api/index.js

Redux container

The way to connect Redux to your React application is through a Redux container, the best practice is to have a clean container meaning that we should not have any JSX code inside, and just passing the component we want to connect to Redux to the connect method.

// Dependencies
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

// Components
import Layout from '../components/Layout';

// Actions
import { fetchPosts, fetchPost } from '../actions';

// Mapping our Redux state to the props
const mapStateToProps = (state) => {
  return {
    posts: state.blog.posts,
    post: state.blog.post
  };
};

// Mapping our actions to the props
const mapDispatchToProps = (dispatch) => bindActionCreators(
  {
    fetchPosts,
    fetchPost
  },
  dispatch
);

// Connecting to Redux and injecting all props to Layout
export default connect(mapStateToProps, mapDispatchToProps)(Layout);
File: src/frontend/app/container/index.js

Blog Reducer

In our blog reducer, we need to create the initial state with our posts and post (for a single post), then once we match our actions we need to update the Redux state by getting the payload from the action. 

// Action Types
import {
  FETCH_POSTS_RESPONSE,
  FETCH_POST_RESPONSE
} from '../actions/actionTypes';

// Initial state
const initialState = {
  posts: [],
  post: {}
};

export default function blogReducer(state = initialState, action) {
  switch (action.type) {
    case FETCH_POSTS_RESPONSE: {
      const { payload: posts } = action;

      // Returning a new state
      return Object.assign({}, state, {
        posts
      });
    }

    case FETCH_POST_RESPONSE: {
      const { payload: post } = action;

      // Returning a new state
      return Object.assign({}, state, {
        post
      });
    }

    default:
      return state;
  }
}
File: src/frontend/app/blog/reducer/index.js

Layout

As you see int the container we are injecting all the props generated by Redux (mapStateToProps and mapDispatchToProps) to the Layout component, I like to have a wrapper to handle the props easier. 

// Dependencies
import React from 'react';
import { Link } from 'react-router-dom';

// Components
import Posts from './Posts';

const Layout = props => (
  <main>
    <h1>
      <Link to="/">Blog Posts</Link>
    </h1>

    <Posts {...props} />
  </main>
);

export default Layout;
File: src/frontend/app/blog/components/Layout.jsx

Posts and Post components

The logic of the blog will be on the Posts component:

// Dependencies
import React, { Component } from 'react';
import { array, func, object } from 'prop-types';

// Components
import Post from './Post';

// Styles
import styles from './Posts.scss';

class Posts extends Component {
  static propTypes = {
    fetchPost: func,
    fetchPosts: func,
    match: object,
    posts: array,
    post: object
  };

  componentDidMount() {
    const {
      fetchPosts,
      fetchPost,
      match: {
        params: {
          id = false
        }
      }
    } = this.props;

    // If the url includes the id we fetch the single post
    // Otherwise we fetch all the posts
    if (id > 0) {
      fetchPost(id);
    } else {
      fetchPosts();
    }
  }

  componentDidUpdate(prevProps) {
    const {
      match: {
        params: {
          id = false
        }
      },
      post,
      posts,
      fetchPost,
      fetchPosts
    } = this.props;

    // When the component updates we validate if the current id is different
    // From the previous one
    const hasDifferentId = id > 0 && id !== prevProps.match.params.id;

    if (hasDifferentId && post.id !== Number(id)) {
      fetchPost(id);
    } else if (!id && posts.length === 0) {
      fetchPosts();
    }
  }

  renderPosts(posts) {
    return posts.map(({id, title, body}) => (
      <Post
        key={id}
        id={id}
        title={title}
        body={body}
      />
    ));
  }

  renderPost({ id, title, body }) {
    return (
      <Post
        key={id}
        id={id}
        title={title}
        body={body}
      />
    );
  }

  render() {
    const {
      posts,
      post,
      match: {
        params: {
          id = false
        }
      }
    } = this.props;

    if (!id && posts.length === 0) {
      return null;
    }

    return (
      <div className={styles.Posts}>
        {id > 0
          ? this.renderPost(post)
          : this.renderPosts(posts)
        }
      </div>
    );
  }
}

export default Posts;
File: src/frontend/app/blog/components/Posts.jsx

Then in the Post component, we will render the title and the body of each post:

Only members can see all the codes
You can Login or Sign Up

File: src/frontend/app/blog/components/Post.jsx

If you run the project you can see the posts like this:

AllPosts-s9cB3.png

Then if you click on a single post you will see it like this:

SinglePost-kmT4e.png

You can find all the code in this repository for this project: https://github.com/D3vEducation/redux-blog.

I hope you find useful this post if you would like to learn more about React and Redux you can get my React books:

avatarLeave a comment

Your comment

Only members can comment. You can Login or Sign Up