DEV Community

Cover image for How To Use Redux with Hooks in a React-TypeScript Project
Gerald
Gerald

Posted on • Edited on

How To Use Redux with Hooks in a React-TypeScript Project

Introduction

Redux is a predictable state container for JavaScript applications. In this tutorial, I will show you how to use redux to manage state in React with TyepeScript and Hooks.

Getting Started

If you are only interested in viewing complete code on GitHub, click here. Otherwise, let's setup the project using Create React App. In this tutorial I will be using yarn but you should be fine with npm as well. In your terminal run the following command

npx create-react-app posts --typescript
Enter fullscreen mode Exit fullscreen mode

This command creates a React Typescript project called posts. To start the development server and view the project in your browser, run the following commands.

cd posts
yarn start
Enter fullscreen mode Exit fullscreen mode

Installations

To use redux:

yarn add @reduxjs/toolkit
Enter fullscreen mode Exit fullscreen mode

To use Redux with React and TypeScript:

yarn add react-redux
yarn add @types/react-redux
Enter fullscreen mode Exit fullscreen mode

To add redux thunk:

yarn add redux-thunk
Enter fullscreen mode Exit fullscreen mode

To add redux devtools:

yarn add redux-devtools-extension
Enter fullscreen mode Exit fullscreen mode

Redux

Setup your redux folder as follows

src
-redux
--actions
--effects
--interfaces
--reducers
--store
--types
Enter fullscreen mode Exit fullscreen mode

The interfaces folder is used for adding all interfaces that can be used across the project. For this tutorial, we will use posts fake data from JSONPlaceholder. In interfaces directory, create a file called Post.ts and add the following code.

export interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}
Enter fullscreen mode Exit fullscreen mode

The interface above defines a single post.

Now we need to setup our types. In the types folder, create a file called PostTypes.ts and add the following code

import { Post } from '../interfaces/Post';

export const GET_POSTS = 'GET_POSTS';

export interface GetPostsStateType {
  posts: Post[];
}

interface GetPostsActionType {
  type: typeof GET_POSTS;
  payload: Post[];
}
export type PostActionTypes = GetPostsActionType;
Enter fullscreen mode Exit fullscreen mode

GetPostsStateType interface is defining what the state will look like; an array of posts. GetPostsActionType interface is defining the action type that you will see later in this tutorial.

In the reducers directory, create a file called PostReducer.ts and add the following code

import {
  GET_POSTS,
  GetPostsStateType,
  PostActionTypes
} from '../types/PostTypes';

const initialStateGetPosts: GetPostsStateType = {
  posts: []
};

export const getPostsReducer = (
  state = initialStateGetPosts,
  action: PostActionTypes
): GetPostsStateType => {
  switch (action.type) {
    case GET_POSTS:
      return {
        ...state,
        posts: action.payload
      };
    default:
      return state;
  }
};

Enter fullscreen mode Exit fullscreen mode

In here, we initialize state of type GetPostsStateType that we defined earlier. We then create a reducer function called getPostsReducer. A reducer takes two parameters; state and action. In our case, state and action are of types initialStateGetPosts and PostActionTypes respectively while the reducer function returns GetPostsStateType. In the switch block, if the case is GET_POSTS, we return whatever is there in the state and update it with the new payload and the default case is state. Note that in a bigger project there would be a lot of cases.

Create another file in the reducers folder and lets call it index.ts. In here, we will combine all our reducers using combineReducers and export them as rootReducer[You can call it anything really] as shown below.

import { combineReducers } from 'redux';
import { getPostsReducer } from './PostReducer';

const rootReducer = combineReducers({
  posts: getPostsReducer
});

export default rootReducer;
Enter fullscreen mode Exit fullscreen mode

Now we will create our store. A store holds the whole state tree of the application. In the store folder, let's have index.ts and add the following code:

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

const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(thunkMiddleware))
);

export type AppState = ReturnType<typeof rootReducer>;
export default store;
Enter fullscreen mode Exit fullscreen mode

All we are doing in here is creating a store from the combined reducers called rootReducer. composeWithDevTools will allow you to monitor global state in your browser if you've installed the Redux Devtools Extension. applyMiddleware(thunkMiddleware) allows us to dispatch async actions

To make the store available to React components, in src/index.ts, we wrap App in Provider and pass the store as shown below

import { Provider } from 'react-redux';
import store from './redux/store';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

At this point, you should see your global state with an empty array of posts as shown below.

Initial State

The only way to change the state in the store is through an action dispatch. In the actions folder create PostActions.ts and add the following code:

import { GET_POSTS, PostActionTypes } from '../types/PostTypes';
import { Post } from '../interfaces/Post';

export const getPostsAction = (posts: Post[]): PostActionTypes => {
  return {
    type: GET_POSTS,
    payload: posts
  };
};
Enter fullscreen mode Exit fullscreen mode

The getPostsAction function accepts an array of posts and returns a type of GET_POSTS and posts data passed to the payload variable. Note that type and payload can be given names of your choice.

To fetch our posts from the fake API, let's create Posts.ts in the effects folder and add the following code.

import { getPostsAction } from '../actions/PostActions';
import { Dispatch } from 'redux';
import { PostActionTypes } from '../types/PostTypes';
export const getPosts = () => {
  return function (dispatch: Dispatch<PostActionTypes>) {
    const POST_URL = 'https://jsonplaceholder.typicode.com/posts';
    fetch(POST_URL, {
      method: 'GET'
    })
      .then(res => res.json())
      .then(data => {
        dispatch(getPostsAction(data));
        return data;
      });
  };
};

Enter fullscreen mode Exit fullscreen mode

All we are doing here is dispatching the getPostsAction and passing it the data from the fake API.

React Component

Finally, in App.ts, we can access our App State. Update App.ts as follows:

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getPosts } from './redux/effects/Posts';
import { Post } from './redux/interfaces/Post';
import { AppState } from './redux/store';

export default function Posts() {
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(getPosts());
  }, [dispatch]);
  const posts = useSelector((state: AppState) => state.posts);
  const postItems = posts.posts.map((post: Post) => (
    <div key={post.id}>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  ));
  return <div>{postItems}</div>;
}

Enter fullscreen mode Exit fullscreen mode

In here, we bring in useDispatch and useSelector from react-redux. The useDispatch hook is used to dispatch actions as needed. In our case, we are passing the getPosts effect to dispatch in the useEffect hook. This will add the data coming from the fake API to our redux store as soon as the App component mounts. At this point your redux store should look like this:

Posts State

useSelector works more or less like mapStateToProps when using connect. It allows us to access app state in a React functional component. In our case we are interested in getting posts from the posts state and that is exactly why we are iterating through posts.posts. Then we display the post title with post.title and body with post.body. Interesting right?

Conclusion

There are many ways you could use redux in your React project. Go with a setup that works for you. Redux can have a lot of boilerplate but comes in handy once the boilerplate code is out of the way.

Happy coding!

Top comments (6)

Collapse
 
awixor profile image
EL HOUCINE AOUASSAR

Great article thanks,
I just wanna know why did you use @reduxjs/toolkit and react-redux at the same time,
I read in a different article that @reduxjs/toolkit is the official, opinionated, toolset for react-redux.

Collapse
 
geraldmaboshe profile image
Gerald

Thanks. I had to install both because I did not use the npx create-react-app my-app --template redux to create my react app. So I needed to install react-redux manually for us to use Redux with React

Collapse
 
isaackomeza profile image
Isaac Komezusenge

Thanks, It's a very interesting topic with clear explanations

Collapse
 
geraldmaboshe profile image
Gerald

Thank you, Isaac.

Collapse
 
gautham495 profile image
Gautham Vijayan

Tremendous post on react redux with hooks in typescript. Finally got it working. Thanks!!!!!

Collapse
 
murtazamzk profile image
murtaza kanpurwala

Great Article thanks a lot,

One question in the state why do we have posts.posts again? can we directly pass in the array from the api?