【React】使ってスッキリ!”カスタムフック”

React

カスタムフックについて、こちらのUdemy講座で学ばせていただいたことをまとめました!

結論めちゃくちゃいいので本当におすすめです!

Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版
「React何となく分かったけど次どうしたら良いか分からない」という人がステップアップするために知っておくべきことを順序立ててハンズオン形式で詰め込みました。本コースを終える頃にはもっとReactのことを好きになっていると思います。

Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版

カスタムフックとは

名前の通り、use◯◯というネーミングをして、自分でhooksを作る(カスタムする)ことができる。

コンポーネントからロジックの部分を切り出すというのが主な目的であり、それにより使い回しができるようになる。

カスタムフックの使用例の実装のため、jsonplaceholderのデモデータを使用し、取得データを表示する機能を作成します。

取得したデータを変換して表示する

import axios from 'axios';
import { UserCard } from './components/UserCard';
import { User } from './types/api/user';
import { useState } from 'react'
import { UserProfile } from './types/userProfile';

function App() {

  const [userProfiles, setUserProfiles] = useState<Array<UserProfile>>([])

  const onClickFetchUser = () => {
    axios.get<Array<User>>("https://jsonplaceholder.typicode.com/users").then((res) => {
      const data = res.data.map((user) => ({
        id: user.id,
        name: `${user.name}(${user.username})`,
        email: user.email,
        address: `${user.address.city}${user.address.suite}${user.address.street}`
      }))
      setUserProfiles(data)
    })

  }
  return (
    <div className="App">
      <button onClick={onClickFetchUser}>データ取得</button>
      {userProfiles.map((user) => (
        <UserCard user={user}/>
      ))}
    </div>
  );
}

export default App;

import { VFC } from "react"
import { UserProfile } from "../types/userProfile"

type Props = {
  user: UserProfile
}

export const UserCard: VFC<Props> = (props) => {
  const { user } = props;

  return (
    <div style={style}>
      <dl>
        <dt>名前</dt>
        <dd>{user.name}</dd>
        <dt>メール</dt>
        <dd>{user.email}</dd>
        <dt>住所</dt>
        <dd>{user.address}</dd>
      </dl>
    </div>
  )
}

「データ取得」ボタンクリックから取得までの流れ。

  • ボタンクリックで関数onClickFetchUserが実行される。
  • jsonplaceholderのデモデータを、使用する形のデータid, name, email, addressに変換し、オブジェクトを作成し、それらを入れた配列をdataとして作成。
  • userProfilesをループしてUserCardをレンダリング
  • UserCardのpropsであるuserに値が入り、名前、メール、住所を取得できる。

ここで使用されているVFCとは

Function Componentの型定義のことでpropsの型を定義している。

未定義のchildrenを渡すとエラーとなります。

データ取得中にローディングを表示する

データ取得中にローディング中であることを表現する機能を実装する。

方法としては、ローディングの情報を持ったstateを用意し、クリック時にローディングをtrueにし、処理が終わったらfalseにするといった処理にする。

errorがあった場合と無かった場合を三項演算子を使って設定する。

三項演算子の基本形
{error ? (A) : (B)}

errorならAを、そうでなければBを。

更に三項演算子を重ねる場合
{error ? (A) : Loading ? (B) : (C)}

errorならAを、LoadingならBを、そうでなければCを。

コードの全貌

import axios from 'axios';
import './App.css';
import { UserCard } from './components/UserCard';
import { User } from './types/api/user';
import { useState } from 'react'
import { UserProfile } from './types/userProfile';

function App() {

  const [userProfiles, setUserProfiles] = useState<Array<UserProfile>>([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(false)

  const onClickFetchUser = () => {
    setLoading(true)
    setError(false)
    axios.get<Array<User>>("https://jsonplaceholder.typicode.com/users")
    .then((res) => {
      const data = res.data.map((user) => ({
        id: user.id,
        name: `${user.name}(${user.username})`,
        email: user.email,
        address: `${user.address.city}${user.address.suite}${user.address.street}`
      }))
      setUserProfiles(data)
    })
    .catch(() => {
      setError(true)
    })
    .finally(() => {
      setLoading(false)
    })
  }

  return (
    <div className="App">
      <button onClick={onClickFetchUser}>データ取得</button>
        <br />
        {error ? (
          <p>データの取得に失敗しました</p>
        ) : loading ? (
          <p>Loading...</p>
        ) : (
          <>
            {userProfiles.map((user) => (
              <UserCard key={user.id} user={user}/>
            ))}
          </>
        )}
    </div>
  );
}

export default App;

.catchにはerrorの場合のstateを設定。

どんな状態でも最終的にはfalseにしたいので、.finallyを使ってfalseとするstateを設定しています。

カスタムフックを作成する

いよいよ本題のカスタムフックを作成していきます。

hook名はuseAllUsersと命名し、名前を全件取得するhooksを作成します。

上記の、App.tsxに記述していたonClickFetchUser関数の中身を丸々移管して、getUsersという関数名とし、returnで返すようにします。

import { useState } from "react"
import { UserProfile } from "../types/userProfile"
import axios from "axios"
import { User } from "../types/api/user"

export const useAllUsers = () => {
  const [userProfiles, setUserProfiles] = useState<Array<UserProfile>>([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(false)

  const getUsers = () => {
    setLoading(true)
    setError(false)
    axios.get<Array<User>>("https://jsonplaceholder.typicode.com/users")
    .then((res) => {
      const data = res.data.map((user) => ({
        id: user.id,
        name: `${user.name}(${user.username})`,
        email: user.email,
        address: `${user.address.city}${user.address.suite}${user.address.street}`
      }))
      setUserProfiles(data)
    })
    .catch(() => {
      setError(true)
    })
    .finally(() => {
      setLoading(false)
    })
  }
  return { getUsers, userProfiles, loading, error }
}

このファイルがカスタムフックになります。

作成したカスタムフックをApp側で呼び出す

上記でreturnされた値を、useAllUsersで受け取る。

上記で、onClickFetchUserの中身をgetUsersに移管したので、onClickFetchUser実行時にgetUsersが実行されるようにします。

import './App.css';
import { UserCard } from './components/UserCard';
import { useAllUsers } from './hooks/useAllUsers';

function App() {

  const { getUsers, userProfiles, loading, error } = useAllUsers();

  const onClickFetchUser = () => getUsers();

  return (
    <div className="App">
      <button onClick={onClickFetchUser}>データ取得</button>
        <br />
        {error ? (
          <p>データの取得に失敗しました</p>
        ) : loading ? (
          <p>Loading...</p>
        ) : (
          <>
            {userProfiles.map((user) => (
              <UserCard key={user.id} user={user}/>
            ))}
          </>
        )}
    </div>
  );
}

export default App;

App.tsxの見通しがかなりよくなりましたね。

以上、ご覧いただきありがとうございました。

コメント