DEV Community

Cover image for How To Use Redux in your React TypeScript App
Ibrahima Ndaw
Ibrahima Ndaw

Posted on • Originally published at ibrahima-ndaw.com

How To Use Redux in your React TypeScript App

Redux is a predictable state container for JavaScript apps. It's a popular library for managing state in React apps. It can offer better developer experience when using along with TypeScript, which is a superset of JavaScript that type-checks code to make it robust and understandable.

In this guide, I will show you how to use Redux in your React TypeScript project by building an app that allows you to add, delete, and show articles.

Let's dive in


You can subscribe to my newsletter to receive the weekly free article in your inbox.


Prerequisites

This tutorial assumes that you have at least a basic understanding of React, Redux, and TypeScript. So, if you're not familiar with these technologies, try to read first this practical guide to TypeScript or this React Redux tutorial - otherwise, let's get started.

Setting up

To use Redux and TypeScript, we need to create a new React app.

To do so, let's open the CLI (command-line interface) and execute this command:

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

Next, let's structure the project as follows:

├── src
|  ├── components
|  |  ├── AddArticle.tsx
|  |  └── Article.tsx
|  ├── store
|  |  ├── actionCreators.ts
|  |  ├── actionTypes.ts
|  |  └── reducer.ts
|  ├── type.d.ts
|  ├── App.test.tsx
|  ├── App.tsx
|  ├── index.css
|  ├── index.tsx
|  ├── react-app-env.d.ts
|  └── setupTests.ts
├── tsconfig.json
├── package.json
└── yarn.lock
Enter fullscreen mode Exit fullscreen mode

The file structure of the project is quite simple. However, there are two things to note:

  • The store folder that contains files related to React Redux.
  • The type.d.ts file that holds the TypeScript types, which can be used now in other files without importing.

That said, we can now install Redux and create our very first store.

So, let's open the project and run the following command:

  yarn add redux react-redux redux-thunk
Enter fullscreen mode Exit fullscreen mode

Or when using npm

  npm install redux react-redux redux-thunk
Enter fullscreen mode Exit fullscreen mode

We also have to install their types as development dependencies to help TypeScript understanding the libraries.

So, let's execute this command again on the CLI.

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

Or for npm

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

Great! With this step forward, we can now create the TypeScript types for the project in the next section.

Create the types

TypeScript Types allows you to set types for your variables, function parameters, and so on.

  • type.d.ts
interface IArticle {
  id: number
  title: string
  body: string
}

type ArticleState = {
  articles: IArticle[]
}

type ArticleAction = {
  type: string
  article: IArticle
}

type DispatchType = (args: ArticleAction) => ArticleAction
Enter fullscreen mode Exit fullscreen mode

Here, we start by declaring The interface IArticle which reflects the shape of a given article. Then, we have ArticleState, ArticleAction, and DispatchType that will serve as types for respectively the state object, the action creators, and the dispatch function provided by Redux.

That said, we now have the needed types to start using React Redux. Let's create the action types.

Create the action types

  • store/actionTypes.ts
export const ADD_ARTICLE = "ADD_ARTICLE"
export const REMOVE_ARTICLE = "REMOVE_ARTICLE"
Enter fullscreen mode Exit fullscreen mode

We need two action types for the Redux store. One for adding articles and another for deleting.

Create the action creators

  • store/actionCreators.ts
import * as actionTypes from "./actionTypes"

export function addArticle(article: IArticle) {
  const action: ArticleAction = {
    type: actionTypes.ADD_ARTICLE,
    article,
  }

  return simulateHttpRequest(action)
}

export function removeArticle(article: IArticle) {
  const action: ArticleAction = {
    type: actionTypes.REMOVE_ARTICLE,
    article,
  }
  return simulateHttpRequest(action)
}

export function simulateHttpRequest(action: ArticleAction) {
  return (dispatch: DispatchType) => {
    setTimeout(() => {
      dispatch(action)
    }, 500)
  }
}
Enter fullscreen mode Exit fullscreen mode

In this tutorial, I will simulate the HTTP request by delaying it for 0.5 seconds. But, feel free to use a real server if you want too.

Here, the function addArticle will dispatch an action for adding a new article, and the method removeArticle will do the opposite and hence, delete the object passed in as an argument.

Create a reducer

A reducer is a pure function that receives the state of the store and an action as parameters and then returns the updated state.

  • store/reducer.ts
import * as actionTypes from "./actionTypes"

const initialState: ArticleState = {
  articles: [
    {
      id: 1,
      title: "post 1",
      body:
        "Quisque cursus, metus vitae pharetra Nam libero tempore, cum soluta nobis est eligendi",
    },
    {
      id: 2,
      title: "post 2",
      body:
        "Harum quidem rerum facilis est et expedita distinctio quas molestias excepturi sint",
    },
  ],
}
Enter fullscreen mode Exit fullscreen mode

As you can see here, we declare an initial state to have some articles to show when the page loads. The state object needs to match the type ArticleState - otherwise, TypeScript will throw an error.

  • store/reducer.ts
const reducer = (
  state: ArticleState = initialState,
  action: ArticleAction
): ArticleState => {
  switch (action.type) {
    case actionTypes.ADD_ARTICLE:
      const newArticle: IArticle = {
        id: Math.random(), // not really unique
        title: action.article.title,
        body: action.article.body,
      }
      return {
        ...state,
        articles: state.articles.concat(newArticle),
      }
    case actionTypes.REMOVE_ARTICLE:
      const updatedArticles: IArticle[] = state.articles.filter(
        article => article.id !== action.article.id
      )
      return {
        ...state,
        articles: updatedArticles,
      }
  }
  return state
}

export default reducer
Enter fullscreen mode Exit fullscreen mode

Next, we have the reducer function that expects the previous state and an action to be able to update the store. Here, we have two actions: one for adding and another for deleting.

With that in place, we can now handle the state with the reducer. Let's now create a store for the project.

Create a store

A Redux store is where your app's state lives.

  • index.tsx
import * as React from "react"
import { render } from "react-dom"
import { createStore, applyMiddleware, Store } from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"

import App from "./App"
import reducer from "./store/reducer"

const store: Store<ArticleState, ArticleAction> & {
  dispatch: DispatchType
} = createStore(reducer, applyMiddleware(thunk))

const rootElement = document.getElementById("root")
render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)
Enter fullscreen mode Exit fullscreen mode

As you can see, we import the reducer function and then pass it as an argument to the method createStore in order to create a new Redux store. The redux-thunk middleware needs to be proceeded as a second parameter as well to the method to be able to handle asynchronous code.

Next, we connect React to Redux by providing the store object as props to the Provider component.

We can now use Redux in this project and access the store. So, let's create the components to get and manipulate the data.

Create the components

  • components/AddArticle.tsx
import * as React from "react"

type Props = {
  saveArticle: (article: IArticle | any) => void
}

export const AddArticle: React.FC<Props> = ({ saveArticle }) => {
  const [article, setArticle] = React.useState<IArticle | {}>()

  const handleArticleData = (e: React.FormEvent<HTMLInputElement>) => {
    setArticle({
      ...article,
      [e.currentTarget.id]: e.currentTarget.value,
    })
  }

  const addNewArticle = (e: React.FormEvent) => {
    e.preventDefault()
    saveArticle(article)
  }

  return (
    <form onSubmit={addNewArticle} className="Add-article">
      <input
        type="text"
        id="title"
        placeholder="Title"
        onChange={handleArticleData}
      />
      <input
        type="text"
        id="body"
        placeholder="Description"
        onChange={handleArticleData}
      />
      <button disabled={article === undefined ? true : false}>
        Add article
      </button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

To add a new article, we will be using this form component. It receives the function saveArticle as a parameter, which allows adding a new article to the store. The article object should follow the type IArticle to make TypeScript happy.

  • components/Article.tsx
import * as React from "react"
import { Dispatch } from "redux"
import { useDispatch } from "react-redux"

type Props = {
  article: IArticle
  removeArticle: (article: IArticle) => void
}

export const Article: React.FC<Props> = ({ article, removeArticle }) => {
  const dispatch: Dispatch<any> = useDispatch()

  const deleteArticle = React.useCallback(
    (article: IArticle) => dispatch(removeArticle(article)),
    [dispatch, removeArticle]
  )

  return (
    <div className="Article">
      <div>
        <h1>{article.title}</h1>
        <p>{article.body}</p>
      </div>
      <button onClick={() => deleteArticle(article)}>Delete</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

The Article component shows an article object.

The function removeArticle has to dispatch to access the store and hence delete a given article. That's the reason why we use here the useDispatch hook providing by Redux to complete the removing action.

Next, The use of useCallback helps to avoid unnecessary re-rendering by memoizing values as dependencies.

We have finally the components needed to add and show the articles. Let's now add the last piece to the puzzle by using them in the App.tsx file.

  • App.tsx
import * as React from "react"
import { useSelector, shallowEqual, useDispatch } from "react-redux"
import "./styles.css"

import { Article } from "./components/Article"
import { AddArticle } from "./components/AddArticle"
import { addArticle, removeArticle } from "./store/actionCreators"
import { Dispatch } from "redux"

const App: React.FC = () => {
  const articles: readonly IArticle[] = useSelector(
    (state: ArticleState) => state.articles,
    shallowEqual
  )

  const dispatch: Dispatch<any> = useDispatch()

  const saveArticle = React.useCallback(
    (article: IArticle) => dispatch(addArticle(article)),
    [dispatch]
  )

  return (
    <main>
      <h1>My Articles</h1>
      <AddArticle saveArticle={saveArticle} />
      {articles.map((article: IArticle) => (
        <Article
          key={article.id}
          article={article}
          removeArticle={removeArticle}
        />
      ))}
    </main>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

The useSelector hook enables access to the state of the store. Here, we pass shallowEqual as a second argument to the method to tell to Redux to use shallow equality when checking for changes.

Next, we rely on useDispatch to dispatch an action for adding articles in the store. Finally, we loop through the array of articles and pass each to the Article component to show it.

With that, we can now browse to the root of the project and then execute this command.

  yarn start
Enter fullscreen mode Exit fullscreen mode

Or for npm

  npm start
Enter fullscreen mode Exit fullscreen mode

If you open http://localhost:3000/ in the browser, you should see this:

app-preview

Great! Our app looks good. With this, we have now finished using Redux in a React TypeScript app.

You can find the finished project in this CodeSandbox

You can find other great content like this on my blog or follow me on Twitter to get notified.

Thanks for reading.

Top comments (5)

Collapse
 
markerikson profile image
Mark Erikson

Hi, I'm a Redux maintainer. I would actually recommend changing most of the code in this article.

First, you should be using our official Redux Toolkit package, which is our recommended approach to writing Redux logic. It's already written in TS, and includes APIs like:

  • configureStore, which automatically adds the thunk middleware and sets up the Redux DevTools Extension for you
  • createSlice, which automatically generates action creators and action types, based on your reducers, and uses Immer internally to let you write "mutating" update logic that gets turned into safe correct immutable updates
  • createAsyncThunk, which automatically dispatches actions based on a promise

We already have an official Redux+TS template for Create-React-App, which comes with RTK already set up:

github.com/reduxjs/cra-template-re...

which you can install with create-react-app --template redux-typescript.

We also recommend inferring the type of the root state based on the return value of your root reducer:

redux-toolkit.js.org/usage/usage-w...

and would recommend against trying to specifically define the set of action types that can be dispatched. Instead, just make sure that the actions a reducer handles are correctly typed, which happens automatically if you're using createSlice.

As a side note, the community consensus is that you should avoid using React.FC:

fettblog.eu/typescript-react-why-i...

These approaches will drastically simplify the amount of actual code and the amount of types you have to write.

Collapse
 
soorajsnblaze333 profile image
Sooraj (PS) • Edited

This is a great way of explaining :). Also do take a look at React Context. It is like an inbuilt redux store for React and it is much easier to setup and use than redux :)

Collapse
 
markerikson profile image
Mark Erikson

Context and Redux are very different tools that solve different problems, with some overlap.

Context is not a "state management" tool. It's a Dependency Injection mechanism, whose only purpose is to make a single value accessible to a nested tree of React components. It's up to you to decide what that value is, and how it's created. Typically, that's done using data from React component state, ie, useState and useReducer. So, you're actually doing all the "state management" yourself - Context just gives you a way to pass it down the tree.

Redux is a library and a pattern for separating your state update logic from the rest of your app, and making it easy to trace when/where/why/how your state has changed. It also gives your whole app the ability to access any piece of state in any component.

So, yes, you can use both of them to pass data down, but they're not the same thing.

For more details, see my posts Redux - Not Dead Yet! and React, Redux, and Context Behavior.

Collapse
 
codepumps profile image
Serkan Sayhan

That's awesome bro. I was thinking to use react-redux with typescript but I dont know how can handle that. I got it now. Thanks for post.

Collapse
 
andrewbaisden profile image
Andrew Baisden

Good read going to give it a try once I get up to speed with TypeScript.