何をやるか
SPAを扱う上で、どのタイミングでローディングの表示をするのか、どのように管理するのか。
結構考えるのめんどくさいと思います。
↓こんなやつ
Redux Toolkitを使えばシンプルにかけたので備忘録として残しておきます
どのようにローディング状態管理しよう…とお考えの方がいれば、その解決の一つになればと思います
使用している技術
- React (reactHooks)
- Redux (Redux-toolkit)
- TypeScript
機能
今回はAPIを叩いてpending中にloadingを表示、
処理が終了したらloadingを非表示という機能を想定して書いていきます。
redux toolkitが用意してくれているextraReducers
という機能を使って実装します!
pending → 実行開始〜結果が帰ってくるまでの間
fulfilled → 正常終了
rejected → エラー
となっているので、以下のように、
pendingの時のみ、isLoadingという項目をtrueにすることで、
APIが実行開始〜結果が帰ってくるまでの間の状態を管理します。
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"
import { fetchGet } from "./common/fetch"
interface selectUserProps {
isLogin: boolean
}
// ↓このAPIを叩く想定で進める
export const getInfo = createAsyncThunk("admin/post",async () => {
const url = "叩くAPIのURL"
const result = await fetchGet(url)
return result
}
)
const sampleSlice = createSlice({
name: "sample",
// ↓初期値
initialState: {
isLoading:false
},
// extraReducersで状態をLoading管理する
extraReducers: (builder) => {
builder.addCase(getInfo.pending, (state, action) => {
// APIの処理が始まったら isLoadingをtrueに
state.isLoading = true
})
builder.addCase(getInfo.rejected, (state, action) => {
// 失敗したら isLoadingをfalseに
state.isLoading = false
})
builder.addCase(getInfo.fulfilled, (state, action) => {
// 成功しても isLoadingをfalseに
state.isLoading = false
})
},
})
// ローディング状態をexport
export const selectLoading = (state: any): selectUserProps => state.sample.isLoading
export default sampleSlice.reducer
// 別に切り分けた関数
import axios from "axios"
axios.defaults.withCredentials = true
export const getInfo = async(url:string) =>{
const result = await axios(url, {
method: 'GET',
)
return result.data
}
UI
UI上では、スピナーを作成して、
先程のloading状態をインポートし、trueの場合だけ作成したスピナーを表示させます。
スピナー
画面全体にLoadingを表示させるコンポーネントです。
(待っている間はユーザー側の入力ができないので、ユーザー操作できるようにするには調整必要です)
import React, { useEffect, useState } from 'react'
import { css } from '@emotion/core'
import { Spinner } from '../../components/atoms/Spinner'
export const FixedSpinner = () => {
return (
<div css={spinnerOverRay}>
<div css={spinnerWrap}>
<Spinner />
</div>
</div>
)
}
const spinnerOverRay = css`
position: fixed;
-ms-transform: translate(-50%, -50%);
height: 100vh;
width: 100vw;
z-index: 999;
background-color: rgba(245, 245, 245, 0.5);
`
const spinnerWrap = css`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`
import React from 'react'
import { css } from '@emotion/core'
export const Spinner = () => {
return <div css={spinner} />
}
export const spinner = css`
width: 50px;
height: 50px;
border: 10px solid #dddddd;
border-top-color: #aaaaaa;
border-radius: 50%;
animation: 1s spin infinite linear;
@keyframes spin {
from {
transform: rotateZ(0deg);
}
to {
transform: rotateZ(360deg);
}
}
`
Loading表示
useSelectorを使って、loading状態を取得し、
trueの場合のみ、先程作成したスピナーを表示します。
import React from 'react'
// components
import { FixedSpinner } from '../../components/block/FixedSpinner'
// store
import { getInfo, selectLoading } from '../../../stores/slices/sampleSlice'
export const Sample = ({ setPath }: any) => {
const dispatch = useDispatch()
const loading = useSelector(selectLoading)
const handleSubmit = () => {
dispatch(getInfo())
}
return (
<div>
// loading が true の時、FixedSpinnerを表示
{loading && <FixedSpinner />}
<button css={SubmitBtn} onClick={handleSubmit}>
API叩く
</button>
</div>
)
}
感想
Redux Toolkitを使えば簡単にloadingの管理ができてハッピーでした
コンポーネントも使いまわせるので、使える部分はこれ使っていこうかな〜と考えております。
もっといい方法あればご教示ください