REST APIと良い感じに通信するHookを自作する
ReactでREST APIと通信する際はReact QueryやSWRといったライブラリを使用されることが多いと思います。
ただ、そのようなライブラリを使用せずに気軽にAPIを叩きたいということもあると思います。そのような時に自分が作っているカスタムHookをご紹介したいと思います。
コンポーネント内で直接APIと通信する(Bad)
Hookにするメリットを感じられるように、初めにHook化をせずにAPIと通信した例を紹介します。
import { useEffect, useState } from "react";
type Example = {
title: string;
};
export const BadExample = () => {
const [data, setData] = useState<Example>();
const [isLoading, setLoading] = useState(true);
const [isError, setError] = useState(false);
useEffect(() => {
// 即時関数
(async() => {
try {
const response = await fetch("/example");
const data = await response.json();
setData(data);
} catch(err) {
setError(true);
} finally {
setLoading(false);
}
})()
}, []);
if(isLoading) {
return <p>...loading</p>
}
if(isError) {
return <p>Error!</p>
}
return <h1>{data?.title}</h1>
};
この例ではコンポーネント内で直接APIと通信しており、コンポーネントとロジックが結びついてしまっています。
このデメリットとして、複数のAPIと通信するコンポーネントではロジックが複雑化することや、API通信を再利用できない(この例で言うとBadExample
以外のコンポーネントで/example
からデータを取得する際に、同じコードをコピペして使い回す必要がある)ことなどが挙げられます。
API通信をHookに分離する(No Bad)
次にAPI通信をしているロジックをそのままHookに切り出した例です。
import { useEffect, useState } from "react";
type Example = {
title: string;
};
export const useExample = () => {
const [data, setData] = useState<Example>();
const [isLoading, setLoading] = useState(true);
const [isError, setError] = useState(false);
useEffect(() => {
// 即時関数
(async() => {
try {
const response = await fetch("/example");
const data = await response.json();
setData(data);
} catch(err) {
setError(true);
} finally {
setLoading(false);
}
})()
}, []);
return { data, isLoading, isError };
};
import { useExample } from "./useExample";
export const NoBadExample = () => {
const { data, isLoading, isError } = useExample();
if(isLoading) {
return <p>...loading</p>
}
if(isError) {
return <p>Error!</p>
}
return <h1>{data?.title}</h1>
};
この例では、useExample
にAPI通信のロジックや状態を持たせることができており、コンポーネント内をスッキリさせることができました。また、useExample
を使用することで/example
とのAPI通信を再利用することができます。
しかし、このパターンでは、別のAPIエンドポイントの/examples
と通信をすることになった時に、同じようなHookが量産されてしまいます。
// /examples と通信する
// useExample との違いは data の型と fetch する URL のみ
import { useEffect, useState } from "react";
type Example = {
title: string;
};
export const useExamples = () => {
const [data, setData] = useState<Example[]>(); // <-- 型が Example[]
const [isLoading, setLoading] = useState(true);
const [isError, setError] = useState(false);
useEffect(() => {
// 即時関数
(async() => {
try {
const response = await fetch("/examples"); // <-- パスが /examples
const data = await response.json();
setData(data);
} catch(err) {
setError(true);
} finally {
setLoading(false);
}
})()
}, []);
return { data, isLoading, isError };
};
このことから、さらに再利用性の高いHookを作ることがGoodであると自分は思いました。
API通信をする汎用性の高いHookを使用する(Good)
useFetch
はGenerics
でdata
の型を受け取り、引数でurl
を受け取ります。
このHookを用いることで、APIと通信をする度にHookを作成する手間がなくなり、気軽にAPIを叩けるようになります。
import { useEffect, useState } from "react";
export const useFetch = <T>(url: string) => {
const [data, setData] = useState<T>(); // <-- Generics で受け取った型を data の型とする
const [isLoading, setLoading] = useState(true);
const [isError, setError] = useState(false);
useEffect(() => {
(async () => {
try {
const res = await fetch(url); // <-- 引数で受け取った url を fetch する
const data = await res.json();
setData(data);
} catch (err) {
console.error(err);
setError(true);
} finally {
setLoading(false);
}
})();
}, []);
return { data, isLoading, isError };
};
/example
からデータを取得する
import { useFetch } from "./useFetch";
type Example = {
title: string;
}
export const GoodExample = () => {
const { data, isLoading, isError } = useFetch<Example>("/example");
if(isLoading) {
return <p>...loading</p>
}
if(isError) {
return <p>Error!</p>
}
return <h1>{data?.title}</h1>
};
/examples
からデータを取得する
import { useFetch } from "./useFetch";
type Example = {
title: string;
}
export const GoodExamples = () => {
const { data, isLoading, isError } = useFetch<Example[]>("/examples");
if(isLoading) {
return <p>...loading</p>
}
if(isError) {
return <p>Error!</p>
}
return (
<ul>
{data?.map((d) => (
<li>{d.title} </li>
))}
</ul>
);
};
最後に
よくあるロジックの切り出し方として、2番目に紹介したNo Badな方法がよく取られがちだと思いますが、そこから一歩踏み込んで、さらに再利用性の高いHookを作成することがより良い解決法だと思います。昨今ではロジックとコンポーネントを分離することが当たり前になってきているので、その一歩先を考えると良いかもしれませんね。
Discussion