DEV Community

Cover image for How to use Redux with TypeScript ?
Tran Minh Tri
Tran Minh Tri

Posted on

How to use Redux with TypeScript ?

In this small tutorial, I will show you how to use Redux with TypeScript.

Disclaimer: I want to do this tutorial as a way to re-do what I learn and at the same time I want to share this knowledge with anyone who needs it. I can't make sure this is the best practice. But if you just want to find out how you can use redux with TypeScript this is one of them.

We will make a small application that simply reaches out to an API then fetches some data and dumps it out the screen. Now, this app may be small and that's okay because our main focuses are :

  • How to setUp a react-typescript project
  • How to setUp a redux store in our project
  • How to create an actionCreator that will perform an Async/Await Request
  • How to use this actionCreator in our components.

This tutorial requires you to know :

  • React
  • Redux
  • ES6 (async/await)
  • Basic of TypeScript

At the end of this tutorial, I will include some useful links for anyone who wants to learn more about TypeScript.

Why TypeScript ?

We know that TypeScript is rising ⭐ .Especially at the enterprise level where we have a lot of code to manage. People want to understand and see how data flow in their web applications.

I mean JavaScript works just fine. There nothing wrong with JavaScript except for the fact that with JavaScript alone it is really hard for you to come into a legacy codebase to build a new feature and this feature includes dozens of files😡 . All these files contain different variables, functions that are not written by you. So it will take you a lot of time to understand all of that without any directions whatsoever.

That is where TypeScript comes in. It will define type for nearly everything. Plus this type thing is also a protection for the app itself and this protection will prevent you from passing in the wrong type and causing more errors that you don't even know about.

The downside of TypeScript however is that it is somewhat re-invent the wheel (debatable). TypeScript does not help you build faster features especially if you are someone who doesn't understand TypeScript well enough. You can think of writing TypeScript as somewhat the same as writing tests.

You will have more errors than before because simply the app doesn't want to accept your type for some reason and you waste more time looking for the answer why 😑 .Believe me, It happens.

But it is undeniable that If we choose to use TypeScript we are choosing to develop an app that will eventually have ten thousand lines of code πŸ“„ and make our future easier by making it more maintainable.

Plus as a developer. There are a lot of companies that desire a developer with experience with TypeScript πŸ’° πŸ’° πŸ’° .And that alone is a good reason to start learning it.

Tutorial :

Start our new react-typeScript project :

You need to choose a directory where you want your app to be and then open a command line in that directory then type this in :

npx create-react-app PROJECT_NAME --template typescript
Enter fullscreen mode Exit fullscreen mode

Okay now go sipping some β˜• β˜• β˜• waiting for the npm packages to do it things

Install packages :

We will install some packages that allow us to work with both typeScript and redux. Open another command line in the project directory and type :

npm install --save @types/react-redux axios react-redux redux redux-thunk 
Enter fullscreen mode Exit fullscreen mode

We just install 5 packages, I will go through each and single one of them with you :

  • types/react-redux : for defining types for react-redux packages. When you use packages with typescript you have to download their type packages as well.
  • axios : this is for working with ajax easier. We actually don't need it but I love it so yea πŸ˜†
  • react-redux : You need this to work connect redux to react
  • redux : Redux library itself
  • redux-thunk : You need this to create an actionCreator

Understand what we want to build :

We gonna build a simple reducer that has an initialState of 3 things:

  • comments: Comment[]
  • loading : boolean
  • error : string | null

We will reach out to an API and fetch the comments array of that post with our input as postId then dump all comments out to the screens.

This is the API we will use : https://jsonplaceholder.typicode.com/comments?postId=1 .The postId will be inserted from an input that we gonna build later.

(PICTURE DEMO HERE)

SetUp Our Reducer

This should be your project folder structure at the moment :
Alt Text

Go and create a folder structure like this :
Alt Text

This is what your normal reducer looks like :
Alt Text

This won't work in TypeScript because we did not define a type for both state and action of this reducer.Now we will define a type for our state first :

interface Comment {
    postId: number,
    id: number,
    name: string,
    email: string,
    body: string
}

interface State {
    comments: Comment[],
    loading: boolean,
    error: string | null 
}
Enter fullscreen mode Exit fullscreen mode

Pretty normal right ? Next we will create a type for our actions.
The thing with our actions is that based on action.type we will have different kind of payloads. In this case we have 3 different action.types so we need 3 different types of action for this.

enum ActionType {
    GET_POST_COMMENTS_PENDING = 'GET_POST_COMMENTS_PENDING',
    GET_POST_COMMENTS_SUCCESS = 'GET_POST_COMMENTS_SUCCESS',
    GET_POST_COMMENTS_FAIL = 'GET_POST_COMMENTS_FAIL'
}

interface actionPending {
    type: ActionType.GET_POST_COMMENTS_PENDING;
}

interface actionSuccess {
    type: ActionType.GET_POST_COMMENTS_SUCCESS;
    payload: string[];
}

interface actionFail {
    type: ActionType.GET_POST_COMMENTS_FAIL;
    payload: string ;
}

type Action = actionPending | actionSuccess | actionFail;

export const commentReducer = (state: State = initialState, action: Action):State => {
...}
Enter fullscreen mode Exit fullscreen mode

I know that our code in this file is messy but It's okay. We will re-factor them later. That's why in the folder redux structure you see I create 2 other folders.

But before close this chapter, I need you to create a compbine File in your reducers to combine all reducers like this :

import { combineReducers } from 'redux';
import { commentReducer } from './index.ts';

const reducers = combineReducers({
comments: commentReducer
});

export default reducers;
//This RootState is required to use useSelector later on 
export type RootState = ReturnType<typeof reducers>;
Enter fullscreen mode Exit fullscreen mode

At this point we already have our reducer ready.

Create an actionCreator and re-factor code :

Create a new file in dir: 'actionTypes' in redux dir. Then put all these codes in there and export our Action :

import { Comment } from '../reducers/index';

export enum ActionType {
    GET_POST_COMMENTS_PENDING = 'GET_POST_COMMENTS_PENDING',
    GET_POST_COMMENTS_SUCCESS = 'GET_POST_COMMENTS_SUCCESS',
    GET_POST_COMMENTS_FAIL = 'GET_POST_COMMENTS_FAIL'
}

interface actionPending {
    type: ActionType.GET_POST_COMMENTS_PENDING;
}

interface actionSuccess {
    type: ActionType.GET_POST_COMMENTS_SUCCESS;
    payload: Comment[];
}

interface actionFail {
    type: ActionType.GET_POST_COMMENTS_FAIL;
    payload: string ;
}

export type Action = actionPending | actionSuccess | actionFail;
Enter fullscreen mode Exit fullscreen mode

Then import our Action and ActionTypes in our reducer File and replace the action.types the put some info in return statement and That's it. This is what our reducer currently looks like :

import { Action, ActionType } from '../actionTypes/index';

interface Comment {
    postId: number,
    id: number,
    name: string,
    email: string,
    body: string
}

interface State {
    comments: Comment[];
    loading: boolean;
    error: string | null;
}

const initialState = {
    comments: [],
    loading: false, 
    error: null 
}

export const commentReducer = (state: State = initialState, action: Action):State => {
    switch(action.type) {
        case ActionType.GET_POST_COMMENTS_PENDING:
            return {
                loading: true 
            } 
        case ActionType.GET_POST_COMMENTS_SUCCESS:
            return {
                loading: false,
                comments: action.payload
            }
        case ActionType.GET_POST_COMMENTS_FAIL:
            return {
                loading: false,
                error: action.payload 
            }
        default: 
            return state;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we will create an actionCreator, if you have create an actionCreator before, then do it yourself then compare with code below :

import axios from 'axios';
import { Dispatch } from 'redux';
import { ActionType, Action } from '../actionTypes';

export const getComments = (postId: string) => {
    return async (dispatch: Dispatch<Action>) => {
        dispatch({
            type: ActionType.GET_POST_COMMENTS_PENDING
        });

        try {
            const { data } = await axios.get(`https://jsonplaceholder.typicode.com/comments?postId=${postId}`);

            dispatch({
                type: ActionType.GET_POST_COMMENTS_SUCCESS,
                payload: data  
            });

        } catch(err) {
            dispatch({
                type: ActionType.GET_POST_COMMENTS_FAIL,
                payload: err.message
            });
        }
    }
} 
Enter fullscreen mode Exit fullscreen mode

There not much of a different from normal actionCreator except you need to define type of dispatch before using it dispatch: Dispatch<Action>

Create a store and connect to our app then use our actionCreator to fetch some data then dump them to the screen :

You need to create a store.ts file in your redux dir.

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers/combine';

export const store = createStore(reducers, {}, applyMiddleware(thunk));
Enter fullscreen mode Exit fullscreen mode

Because this is a simple app I will build everything in App.tsx file.Go to your index.tsx file in your src directory to provide your store:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {store} from './redux/store'; 
import { Provider } from 'react-redux';

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

We also need to create a custom hook to be able to use useSelector with TypeScript, you just need to create a file called useTypeSelector and pass this in :

import { useSelector, TypedUseSelectorHook } from 'react-redux';
import { RootState } from '../redux/reducers/combine';
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
Enter fullscreen mode Exit fullscreen mode

This is documented in React-Redux with TypeScript. That is just how it is to use useSelector so there nothing much to talk about this.

Okay now go to App.tsx, After this file I think we are done for this tutorial :

import React, {useState} from 'react';
import { getComments } from './redux/actionCreators/getComment';
import { useDispatch } from 'react-redux';
import { useTypedSelector } from './hooks/useTypeSelector';

function App() {
  const dispatch = useDispatch();
  const [postId, setPostID] = useState("");
  const { comments, loading, error } = useTypedSelector((state) => state.comments);

  const onSubmitHandler = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    await dispatch(getComments(postId));
  }

  return (
      <>
      <div>
        <form onSubmit={onSubmitHandler}>
          <input type={"number"} value={postId} onChange={(e) => setPostID(e.target.value)} />
          <button type="submit"> submit </button>
        </form>
      </div>

      {
        loading ? (
          <div>Loading...</div>
        ) : (
          <ul>
            {
              comments.map((comment) => {
                return(<li key={comment.id}>{comment.body}</li>)
              })
            }
          </ul>
        )
      }
      </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

And that is it, you may notice there aren't many differents with normal React components except event: React.FormEvent<HTMLFormElement> and import { useTypedSelector } from './hooks/useTypeSelector';

This is our final result :
Alt Text

Now this is ugly πŸ˜… I have to admit it. But this is not a tutorial about CSS. You can always add that later and it is really trivial to focus on CSS in this tutorial.

My point is this tutorial will help you understand how to setUp redux-store with React using TypeScript, how to create an ActionCreator and how to use this ActionCreator in your app.

Here is the source code for the app : Here

If you want to dive deeper into this topic, please visit this link :
Here

I hope this will help you πŸ˜‚ the main reason I do this tutorial is solidating my knowledge but if it helps you in some way then I glad. Please give me a star on Github if this helps you :3

-- peace --

Top comments (2)

Collapse
 
markerikson profile image
Mark Erikson

Hi, I'm a Redux maintainer. Unfortunately, the patterns shown in this article are very outdated and actively go against how we recommend writing Redux code today :(

Please note that "modern Redux" code is very different than what most older tutorials show. We've introduced newer APIs like Redux Toolkit, which is a set of utilities that provide a light abstraction to simplify the most common Redux tasks, and the React-Redux hooks API, which is generally easier to use than the traditional connect API.

I strongly recommend reading through the newly rewritten official tutorials in the Redux docs, which have been specifically designed to teach you how Redux works and show our recommended practices:

  • "Redux Essentials" tutorial: teaches "how to use Redux, the right way", by building a real-world app using Redux Toolkit
  • "Redux Fundamentals" tutorial: teaches "how Redux works, from the bottom up", by showing how to write Redux code by hand and why standard usage patterns exist, and how Redux Toolkit simplifies those patterns

For Redux+TS specifically, follow the patterns shown in the Redux core docs "Usage with TypeScript" page.

You should also read through the Redux "Style Guide" docs page, which explains our recommended patterns and best practices. Following those will result in better and more maintainable Redux apps.

In addition, the easiest way to start a new project is with the official Redux+TS template for Create-React-App. It comes with Redux Toolkit and the React-Redux hooks API already set up when the project is created, as well as pre-typed React-Redux hooks.

Collapse
 
tris909 profile image
Tran Minh Tri

Hi Hey, Thank you for your long reply. This tutorial here is actually from a Udemy code that I have done recently to study TypeScript.

If everyone read this. Please follow this guide link for the newest best practices.

Thank you all