【React】スケルトンスクリーンの実装方法

## はじめに
　本記事は、プログラミング初学者が、学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。
　そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。
　間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。

## スケルトンスクリーンの実装方法

HTTPリクエストをするたにaxiosを使用します。

```:ターミナル
$ npm i axios
```

```src/urls/index.js
// リクエストに使用するURIを定義
const DEFAULT_API_LOCALHOST = 'http://localhost:3001/api/v1'

export const usersIndex = `${DEFAULT_API_LOCALHOST}/users`
```

```src/apis/users.js
import axios from 'axios';
import { usersIndex } from '../urls/index'

export const fetchUsers =() => {
  // ../urls/indexのusersIndexに記述したURLに対してリクエスト
  return axios.get(usersIndex)
  // 成功した場合には.then以降
  // 返り値をresという名前で取得し、res.dataでレスポンスの中身をreturn
  .then(res => {
    return res.data
  })
  // 失敗した場合には.catch以降
  // .catch以降でエラーをコンソールに出力
  .catch((e) => console.error(e))
}
```

状態を管理するための定数を定義します。

```src/constants.js
// 状態に関する定数を定義
export const REQUEST_STATE = {
  INITIAL: 'INITIAL',
  LOADING: 'LOADING',
  OK: 'OK',
}

```

usersReducerを作成

```src/reducers/users.js
import { REQUEST_STATE } from "../constants";

export const initialState = {
  // GET APIの状態を表す
  fetchState: REQUEST_STATE.INITIAL,
  // APIから取得したデータを配列で受け取る
  usersList: [],
};

export const usersActionTypes = {
  FETCHING: "FETCHING",
  FETCH_SUCCESS: "FETCH_SUCCESS",
};


export const usersReducer = (state, action) => {
  switch (action.type) {
    // action.typeがusersActionTypes.FETCHINGであれば
    case usersActionTypes.FETCHING:
      return {
        ...state,
        fetchState: REQUEST_STATE.LOADING,
      };
    // action.typeがusersActionTypes.FETCH_SUCCESSであれば
    case usersActionTypes.FETCH_SUCCESS:
      return {
        fetchState: REQUEST_STATE.OK,
        usersList: action.payload.users,
      };
    default:
      throw new Error();
  }
}
```

Usersコンポーネントを作成します。
スタイリングにstyled-componentsとMaterial-UIを使用しています。

```containers/Users.jsx
import React, { useEffect, useReducer } from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
// components
import { Skeleton } from '@mui/material';
// apis
import { fetchUsers } from "../apis/users";
// reducer
import { initialState, usersActionTypes, usersReducer } from "../reducers/users";
// constants
import { REQUEST_STATE } from "../constants";


// styled-componentsでスタイルを指定
const UsersContentsList = styled.div`
  display: flex;
  justify-content: space-around;
  margin-bottom: 150px;
`;

const UsersContentWrapper = styled.div`
  width: 300px;
  height: 300px;
  padding: 48px;
`;

const UsersImageNode = styled.img`
  width: 100%;
`;
const MainText = styled.p`
  color: black;
  font-size: 18px;
`;

const SubText = styled.p`
  color: black;
  font-size: 12px;
`;


export const Users = () => {
  const [state, dispatch] = useReducer(usersReducer, initialState);
  useEffect(() => {
    // dispatchはreducerを通じてstateを変更する
    // usersActionTypes.FETCHINGはreducerのcase usersActionTypes.FETCHING以下の処理を実行
    dispatch({ type: usersActionTypes.FETCHING });
    fetchUsers()
    .then((data) =>
      dispatch({
        // usersActionTypes.FETCH_SUCCESSはreducerのcase usersActionTypes.FETCH_SUCCESS以下の処理を実行
        type: UsersActionTypes.FETCH_SUCCESS,
        payload: {
          users: data.users
        }
      })
    )
  }, [])

  return (
    <>
      <UsersContentsList>
      {/* JSの変数を参照するため{}で括る */}
      {/* 三項演算子で状態によって表示を変化させる */}
        {
          state.fetchState === REQUEST_STATE.LOADING ?
            <>
            {/* fetchStateがREQUEST_STATE.LOADINGであれば以下 */}
            {/* MUIのSkeletonを使用。形やサイズを指定 */}
              <Skeleton variant="rect" width={300} height={250} />
              <Skeleton variant="rect" width={300} height={250} />
              <Skeleton variant="rect" width={300} height={250} />
            </>
          :
          /* fetchStateがREQUEST_STATE.LOADINGでなければ以下 */
          state.usersList.map((user, index) =>
            <Link to={`/users/${user.id}`} key={index} style={{ textDecoration: 'none' }}>
              <UsersContentWrapper>
                <UsersImageNode src={user.image} />
                <MainText>{user.name}</MainText>
                <SubText>{user.text}</SubText>
              </UsersContentWrapper>
            </Link>
          )
        }
      </UsersContentsList>
    </>
  )
}

```