React and Redux with Typescript

npx create-react-app react-redux-with-typescript --typescript

A tsx file is a file in typescript that can take amounts of JSX.

Props and State in a class based component

React.Component takes two generics, one for the props and one for the state.

Use of interface to describe the props a component can receive and pass :

import React from "react";  
import ReactDOM from "react-dom";
interface AppProps {
  color: string;  
}  
class App extends React.Component<AppProps> {  
render() {
    return <div>{this.props.color}</div>;
  }
}  
ReactDOM.render(<App color="red" />, document.getElementById("root"));

Use of interface to describe the state of the components:

import React from "react";
import ReactDOM from "react-dom";
interface AppProps {
  color: string;
}
interface AppState {
  counter: number;
}
class App extends React.Component<AppProps, AppState> {
  constructor(props: AppProps) {    
    super(props);
    this.state = { counter: 0 };
  }
  onIncrement = (): void => {
    this.setState({ counter: this.state.counter + 1 });  
};
  onDecrement = (): void => {
    this.setState({ counter: this.state.counter - 1 }); 
 };
  render() {
    return 
    (
      <div>
        <button onClick={this.onIncrement}>Increment</button>
        <button onClick={this.onDecrement}>Decrement</button>                   
        {this.state.counter}
     </div>
   );
  }
}
ReactDOM.render(<App color="red" />, document.getElementById("root"));

Props and State in a functionnal based component

interface AppProps {
  color: string;
}
const App = (props:AppProps): JSX.Element => {
   return <div>{props.color}</div>
}

Redux setup

We need to install manually the definition file type for react-redux:

yarn add @types/react-redux

Action types

Define the action types as an enum in src/actions/types.ts

export enum ActionTypes {  fetchTodos  }

Action creators

When creating the action creators, specify a interface that describe the data fectch from a third party API if any, and an interface that describe the action object. These interaces are used on the get method of axios and the dispatch method of redux:

import axios from "axios";
import { Dispatch } from "redux";
import { ActionTypes } from "./types";
const url = "https://jsonplaceholder.typecode.com/todos";

//home made interface that describe the datas fetched  export interface  Todo {
  id: number;
  title: string;
  completed: boolean;  
}
  
//interface to describe the action object  
export interface FetchTodosAction {
    type: ActionTypes.fetchTodos,
    payload: Todo[]
}
  
export const fetchTodos = () => {
  return async (dispatch: Dispatch) => {
    const response = await axios.get<Todo[]>(url);  
    dispatch<FetchTodosAction>({
      type: ActionTypes.fetchTodos,
      payload: response.data
    });
  };
};

Todo reducer

A reducer takes two params, the state and the action. Both of them are typed using a custom interface.

import { Todo, FetchTodosAction } from "../actions";
import { ActionTypes } from "../actions/types";

export const todosReducer = (state: Todo[] = [], action: FetchTodosAction) => {
  switch (action.type) {
    case ActionTypes.fetchTodos:
      return action.payload;
    default:
      return state;
  }
};  

Root reducer

The root reducer is good place to define an interface that describe the global redux state of the application. The combineReducer method from redux can be typed using this interface:

import { combineReducers } from "redux";
  import { todosReducer } from "./todos";
  import { Todo } from "../actions";
  
export interface StoreState {
  todos: Todo[];
}

export const reducers = combineReducers<StoreState>({  todos: todosReducer  }  );

Connecting a component to redux

We need to avoid default export as much as possible with typescript so in this example is called _App and the wrapped one is call App.

Here we need to type the props of the component. Therefor the return of mapStateToProps:

The StoreState interface is used to type the parameter of mapStateToProps, and the Todo interface is used to type the return of mapStateToProps.

import React from "react";
import { connect } from "react-redux";
import { Todo, fetchTodos } from "../actions";
import { StoreState } from "../reducers";  

interface AppProps {
  todos: Todo[];
  fetchTodos(): typeof fetchTodos;
}

class _App extends React.Component<AppProps> {
  render() {
    return <div>Hi there!</div>;
  }
}
  
const mapStateToProps = (state: StoreState): { todos: Todo[] } => {
  return {
    todos: state.todos
  };
};