カスタムフックについて、こちらの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の見通しがかなりよくなりましたね。
以上、ご覧いただきありがとうございました。
コメント