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

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

結論めちゃくちゃいいのでこの講座は本当におすすめです!

【教育・学習】資格・学習
総合情報サイト「コレダ!」がお届けする教育・学習における資格・学習の総合情報サイトです。

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の見通しがかなりよくなりましたね。

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

コメント