カスタムフック
カスタムフックとは
・hooksの各機能を使用
・コンポーネントからロジックを分離
・使い回し、テスト容易、見通しが良くなる
・自由に作成できる
カスタムフックなしで実装
$ mkdir ./src/components $ mkdir -p ./src/types/api $ touch ./src/components/UserCard.tsx $ touch ./scr/types/userProfile.ts $ touch ./scr/types/api/user.ts
export type User = { id: number; name: string; username: string; email: string; address: { street: string; suite: string; city: string; zipcode: string; geo: { lat: string; lng: string; }; }; phone: string; website: string; company: { name: string; catchPhrase: string; bs: string; }; };
export type UserProfile = { id: number; name: string; email: string; address: string; };
import { VFC } from "react"; import { UserProfile } from "../types/userProfile"; type Props = { user: UserProfile; }; export const UserCard: VFC<Props> = (props) => { const { user } = props; const style = { border: "solid 1px #ccc", borderRadius: "8px", padding: "0 16px", margin: "8px" }; 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> ); };
・データ取得
・ローディング
・エラー
import "./styles.css"; import { UserCard } from "./components/UserCard"; import axios from "axios"; import { User } from "./types/api/user"; import { useState } from "react"; import { UserProfile } from "./types/userProfile"; export default 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 style={{ color: "red" }}>データの取得に失敗しました</p> ) : loading ? ( <p>Loading...</p> ) : ( <> {userProfiles.map((user) => ( <UserCard key={user.id} user={user} /> ))} </> )} </div> ); }
コンポーネントの肥大化。
カスタムフックを使用して実装
$ mkdir ./src/hooks $ touch ./src/hooks/useAllUsers.ts
import axios from "axios"; import { useState } from "react"; import { UserProfile } from "../types/userProfile"; 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 }; };
import "./styles.css"; import { UserCard } from "./components/UserCard"; import { useAllUsers } from "./hooks/useAllUsers"; export default function App() { const { getUsers, userProfiles, loading, error } = useAllUsers(); const onClickFetchUser = () => getUsers(); return ( <div className="App"> <button onClick={onClickFetchUser}>データ取得</button> <br /> {error ? ( <p style={{ color: "red" }}>データの取得に失敗しました</p> ) : loading ? ( <p>Loading...</p> ) : ( <> {userProfiles.map((user) => ( <UserCard key={user.id} user={user} /> ))} </> )} </div> ); }