Redux + React ^16.8 (hooks) + Typescript, an industrial implementation
Tags: Typescript, React, Redux

Basically, the aim is to be as effective as possible when both teams (Front / Back) work together. They often need to update the API, change endpoints, add / remove some keys of the exposed entities (DTO), rename entities… And at start that wasn’t easy to synchronize their entities with thoose defined internally in Typescript and used in React.

The first idea was to find a way to synchronize them. Our stack is using Java+ Spring boot + Spring fox and thus exposes a swagger (AKA Open Api 2.0) definition of the full API (endpoints / entities).

Parse the API

The first objective was to find a way to convert swagger definition to Typescript objects & enums. various tools exists that may suit your needs, but we didn’t find an existing tool that suits ours, so we built a script that fetch the last API definition and generate several files:

types.ts which will contains all the exposed DTO & Enums.

// types.ts

// ... export enum TaskType {

urgent = 'urgent',

normal = 'normal'

} export interface TaskDTO {

type: TaskType,

weight: number;

color: boolean;

taskId: number;

value: String;

} // ...

routes.ts which will contains all the action creators to call each endpoint, with the parameters & return type. restApiRequest is our own implementation of Fetch API that respects redux FSA (see Typescript section).

// routes.ts import * as dto from './types';

import { Method, restApiRequest } from '@internal/js-rest-api'; // ... export type PostTaskResponse = dto.TaskDTO;

export function postTasks(body: dto.TaskDTO, requestActionType?: string, responseActionType?: string) {

return restApiRequest({

method: Method.POST,

route: `/api/tasks`,

isSecure: true,

body,

requestActionType,

responseActionType

});

} // ...

Typescript

Based on Redux Flux Standard Action (FSA), we built up Typescript interfaces to have a contract of what will be exchanged later on. Basically we have three types of actions that can be either a RequestAction (When we asking the server for something), a ResponseAction (When the server answers back) or an InternalAction (Simple redux action that will be listened by any other reducer or middleware internally).

We need to distinguish responses from server which may be a success or an error, thus implementing ResponseActionSuccess and ResponseActionFailure.

We basically end up with following interfaces in Typescript. Pay attention to the exported ones (1-3) which will be implemented by our actions later. Also pay attention to the keyword readonly here that will help us Typesafe our reducers & middlewares later.

// Rest Api response when no error

interface ResponseActionSuccess<T, B> {

readonly type: T;

payload: {

code: number;

body: B;

};

error: false;

}



// Rest Api response when an error occurs

interface ResponseActionFailure<T> {

readonly type: T;

payload: RestApiError;

error: true;

} // 1 - Rest Api request

export interface RequestAction<T, P> {

readonly type: T;

payload: P;

} // 2 - Rest Api request may result a success or an error response

export type ResponseAction<T, P> = ResponseActionSuccess<T, P> | ResponseActionFailure<T>; // 3 - Other action types (internal to ducks, not involving rest request / response)

export interface InternalAction<T, P> {

readonly type: T;

payload: P;

}

If you are not familiar with Typescript, you might think, WTF is that ? It’s just some generic Typescript definitions. well in our `InternalAction<T, P>`, T will be our constant (keyword readonly) used in the reducers, and P the payload that will be contained in the reducer’s action parameter… Quite simple though.