APIからデータを取得する
ウィジェットでAPIのデータが必要なときは、useFetchフックを使います。
import { useFetch } from "~lib/use-fetch";
type Weather = {
readonly temperature: number;
};
export const Widget = () => {
const { data } = useFetch<Weather>("https://api.example.com/weather");
return <div className="text-6xl">{data.temperature}°C</div>;
};
ウィジェットはデータを受け取ってから描画されるので、dataは常にそろっています。読み込み中かどうかを確認する必要はありません。
ブラウザーのfetchではなく、useFetchを使ってください。リクエストはBlotchのサーバーを経由するので、ブラウザーからのリクエストをブロックするAPIでも使えます。
レスポンスに型を付ける
レスポンスの形を型パラメーターとして渡すと、dataで入力補完がすべて効くようになります。
type Quote = {
readonly text: string;
readonly author: string;
};
const { data } = useFetch<Quote[]>("https://api.example.com/quotes");
data[0].author; // string
型を手で書く必要はありません。ウィジェットスタジオのネットワークタブで、レスポンスの横にある型定義を作成を選ぶと、そのレスポンスのTypeScriptの型をコピーできます。
入力項目を使う
入力項目をuseInputsで読み取り、URLに組み込みます。ユーザーが入力した値は、スペースや記号が崩れないようencodeURIComponentで囲んでください。
import { useFetch } from "~lib/use-fetch";
import { useInputs } from "~lib/use-inputs";
export const Widget = () => {
const { CITY } = useInputs();
const { data } = useFetch<Weather>(
`https://api.example.com/weather?city=${encodeURIComponent(CITY)}`,
);
return <div>{data.temperature}°C</div>;
};
APIキー
ウィジェットのコードは公開されるので、APIキーをコードに直接書かないでください。非公開入力項目として保存し、ヘッダーで送ります。
const { API_KEY } = useInputs();
const { data } = useFetch<Weather>("https://api.example.com/weather", {
headers: { "X-Api-Key": API_KEY },
});
GoogleやSpotifyのように、OAuthでサインインするサービスには、代わりに連携を使います。
データを送信する
2つ目の引数には、method、headers、bodyなど、ブラウザーのfetchと同じオプションを指定できます。
const { data } = useFetch<SearchResult>("https://api.example.com/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: "weather" }),
});
bodyには、JSONなどのテキストを指定できます。フォームを送信するときは、URLSearchParamsやFormDataを指定します。ファイルは送信できません。
リクエストを毎回同じにする
ウィジェットのコードは、データを待つ間に何度か実行されることがあります。また、レスポンスは、それを要求したリクエストとまったく同じリクエストにしか返されません。そのため、URL、ヘッダー、ボディは毎回同じものを組み立ててください。
ランダムなIDや現在時刻は実行のたびに変わるので、そのレスポンスは使われません。何度か試したあと、Blotchはリクエストをやめ、ウィジェットの代わりにエラーを表示します。こうした値は、ウィジェットの外で一度だけ作ってください。
const DEVICE_ID = Math.random().toString(36).slice(2);
export const Widget = () => {
const { data } = useFetch<Session>("https://api.example.com/sessions", {
method: "POST",
body: JSON.stringify({ deviceId: DEVICE_ID }),
});
return <div>Signed in as {data.name}</div>;
};
useStateでも値は保持できません。最初のデータを待つ間に、Reactがウィジェットの状態を最初から作り直すためです。
リクエストを順番に行う
dataは常にそろっているので、2つ目のリクエストで1つ目の結果を使えます。
const { data: user } = useFetch<User>("https://api.example.com/me");
const { data: posts } = useFetch<Post[]>(
`https://api.example.com/users/${user.id}/posts`,
);
リクエストは1つずつ順番に実行されるので、リクエストが増えるほど、ウィジェットの準備にかかる時間が長くなります。取得するのは表示するものだけにしましょう。
キャッシュ
proxyCacheにttlをミリ秒で指定すると、Blotchはその間レスポンスを保存しておきます。
const { data } = useFetch<Weather>("https://api.example.com/weather", {
proxyCache: { ttl: 30 * 60 * 1000 }, // 30分
});
ttlが切れるまでは、どのフレームからのリクエストでも、同一のリクエストにはAPIに問い合わせ直さず、保存されたレスポンスが返されます。URL、メソッド、ヘッダー、ボディがすべて一致すれば、同一のリクエストとみなされます。そのため、ロンドンの天気を表示するフレームはすべて1つのレスポンスを共有し、パリには別のレスポンスが使われます。
天気やニュースの見出しのようにデータがゆっくり変わるときや、APIの応答が遅い、レート制限がある、リクエストごとに料金がかかるといったときに使いましょう。常に最新でなければならないデータには使わないでください。
- 非公開のリクエストはキャッシュされません。
Authorization、Proxy-Authorization、Cookie、X-Api-Keyのいずれかのヘッダーを含むリクエストは、レスポンスが特定の個人のものかもしれないため、常にAPIに送られます。URLに含まれるキーは見分けられないので、キャッシュするのは誰に対しても同じになるレスポンスだけにしてください。 - エラーもキャッシュされます。
ttlを短くしておけば、不正なレスポンスから早く立ち直れます。 - キャッシュを途中で消去することはできません。また、
ttlが長すぎる場合は、Blotchが許可する上限まで短縮されます。
エラー
dataには、404 Not Foundのようなエラーも含めて、APIが返したものがそのまま入ります。APIにエラーの形式がある場合は、それを確認します。
type WeatherResponse =
| { readonly temperature: number }
| { readonly error: { readonly message: string } };
export const Widget = () => {
const { data } = useFetch<WeatherResponse>("https://api.example.com/weather");
if ("error" in data) {
return <div>Weather unavailable: {data.error.message}</div>;
}
return <div>{data.temperature}°C</div>;
};
APIが停止しているなど、リクエスト自体ができなかった場合は、Blotchがウィジェットの代わりにエラーを表示します。自分で処理する必要はありません。
リクエストはフレームからではなくBlotchのサーバーから送られるので、192.168.1.10のような家庭内ネットワークのアドレスには届きません。
再試行
APIによっては「まだ処理中」と応答し、もう一度問い合わせるよう求めるものがあります。そうしたレスポンスでtrueを返すconditionをretryに渡すと、useFetchは少し待ってから、最大count回まで問い合わせ直します。
const { data } = useFetch<Report>("https://api.example.com/report", {
retry: {
condition: (report) => report.status === "pending",
count: 5,
},
});
レスポンスの形式
- JSONは自動で解析されます。
- XML、RSS、Atomのレスポンスは、
<?xmlで始まるものがプレーンなオブジェクトに変換されます。要素はキーに、属性は@_で始まるキーになるので、<rss version="2.0">はdata.rss["@_version"]になります。 - それ以外のレスポンス(プレーンテキストやHTMLなど)は、
stringとして届きます。
XMLのAPIのレスポンスが<?xmlで始まらない場合は、output: "force"を設定すれば変換されます。XMLをテキストのまま受け取るには、output: "pass-through"を設定します。
すべてのオプションについては、リファレンスのuseFetchを参照してください。