TypeScript & Redux Middleware
2020-08-17

redux-thunk

$ yarn add axios redux-thunk

index.tsx

import {applyMiddleware, createStore} from "redux";
import rootReducer from "./modules";
import Thunk from 'redux-thunk';

const store = createStore(rootReducer, applyMiddleware(Thunk));

GET https://api.github.com/users/2paradise

quicktype 사이트를 이용하여 json data의 typescript interface 를 쉽게 얻을 수 있다.

Convert JSON to Swift, C#, TypeScript, Objective-C, Go, Java, C++ and more * quicktype

api/github.ts

import axios from 'axios';

export async function getUserProfile(username: string) {
    const response = await axios.get<GithubProfile>(`https://api.github.com/users/${username}`);
    return response.data;
};

export type GithubProfile = {
    login:               string;
    id:                  number;
    node_id:             string;
    avatar_url:          string;
    gravatar_id:         string;
    url:                 string;
    html_url:            string;
    followers_url:       string;
    following_url:       string;
    gists_url:           string;
    starred_url:         string;
    subscriptions_url:   string;
    organizations_url:   string;
    repos_url:           string;
    events_url:          string;
    received_events_url: string;
    type:                string;
    site_admin:          boolean;
    name:                null;
    company:             null;
    blog:                string;
    location:            null;
    email:               null;
    hireable:            null;
    bio:                 null;
    twitter_username:    null;
    public_repos:        number;
    public_gists:        number;
    followers:           number;
    following:           number;
    created_at:          Date;
    updated_at:          Date;
};

modules/github/actions.ts

import {GithubProfile} from "../../api/github";
import {AxiosError} from "axios";
import {createAsyncAction} from "typesafe-actions";

export const GET_USER_PROFILE = 'github/GET_USER_PROFILE';
export const GET_USER_PROFILE_SUCCESS = 'github/GET_USER_PROFILE_SUCCESS';
export const GET_USER_PROFILE_ERROR = 'github/GET_USER_PROFILE_ERROR';

// payload type generic 으로 설정
export const getUserProfileAsync = createAsyncAction(
    GET_USER_PROFILE,
    GET_USER_PROFILE_SUCCESS,
    GET_USER_PROFILE_ERROR)<undefined, GithubProfile, AxiosError>();

modules/github/types.ts

import * as actions from './actions';
import {ActionType} from "typesafe-actions";
import {GithubProfile} from "../../api/github";

export type GithubAction = ActionType<typeof actions>;
export type GithubState = {
    userProfile: {
        loading : boolean;
        data: GithubProfile | null;
        error: Error | null;
    }
}

modules/github/thunks.ts

import {Dispatch} from "redux";
import {getUserProfileAsync} from "./actions";
import {getUserProfile} from "../../api/github";

export function getUserProfileThunk(username: string){
    return async (dispatch: Dispatch) => {
        const {request, success, failure} = getUserProfileAsync;
        dispatch(request());
        try{
            const userProfile = await getUserProfile(username);
            dispatch(success(userProfile));
        } catch (e) {
            dispatch(failure(e));
        }
    };
}

modules/github/reducer.ts

import {createReducer} from 'typesafe-actions';
import {GithubAction, GithubState} from "./types";
import {GET_USER_PROFILE, GET_USER_PROFILE_ERROR, GET_USER_PROFILE_SUCCESS} from "./actions";

const initialState: GithubState = {
    userProfile: {
        loading: false,
        error: null,
        data: null
    }
}

const github = createReducer<GithubState, GithubAction>(initialState, {
    [GET_USER_PROFILE]: (state) => ({
        ...state,
        userProfile: {
            loading: true,
            error: null,
            data: null
        }
    }),
    [GET_USER_PROFILE_SUCCESS]: (state, action) => ({
        ...state,
        userProfile: {
            loading: false,
            error: null,
            data: action.payload
        }
    }),
    [GET_USER_PROFILE_ERROR]: (state, action) => ({
        ...state,
        userProfile: {
            loading: false,
            error: action.payload,
            data: null
        }
    })
});

export default github;

modules/github/index.ts

export {default} from './reducer';
export * from './actions';
export * from './types';
export * from './thunks';

modules/index.ts

import {combineReducers} from "redux";
import counter from "./counter";
import todos from "./todos";
import github from "./github";

const rootReducer = combineReducers({
    counter,
    todos,
    github
});

export default rootReducer;
export type RootState = ReturnType<typeof rootReducer>;
© 2020, Built with Gatsby, React, Typscript, styled-components