React NativeでReact Queryを使ってみよう
React Native React Queryを使ってみようという話。ネタバレすると、今回はuseQuery
のみで、useMutation
などは使わない。
TL;DR
今回のコードはこちらのリポジトリで全容を確認できる。
プロジェクトの準備
まずはプロジェクトを作成する。
expo init --template expo-template-blank-typescript cd project-name
デザインをしやすくするためにNative Baseをインストールする。
yarn add native-base expo install react-native-svg
ノッチやステータスバーなOSのインターフェース要素の周辺にコンポーネントを適切に配置するためにreact-native-safe-area-context
をインストールする。このパッケージはnative-baseが依存しているので必要。
expo install react-native-safe-area-context
React Queryをインストールする。
yarn add @tanstack/react-query
React Queryを使ってみる前に必要な作業を行なっていく。
App.tsx
まずは、App.tsxの中身を分離させてMain.tsxとHomeScreen.tsxを作成する。
App.tsxではReact Queryの<QueryClientProvider />
やNative Baseの<NativeBaseProvider>
などでMain.tsxのMain
コンポーネントを囲むようにする。こうすることで、Main
コンポーネント内で囲んだプロバイダーのフックが使えるようになる利点がある。
// App.tsx import { SafeAreaProvider } from "react-native-safe-area-context"; import { QueryClient, QueryClientProvider, } from "@tanstack/react-query"; import Main from "./Main"; const queryClient = new QueryClient(); export default function App() { return ( <QueryClientProvider client={queryClient}> <SafeAreaProvider> <Main /> </SafeAreaProvider> </QueryClientProvider> ); }
QueryClient
queryClient
はキャッシュされたデータとのコミニュケーションを行うために使用される。そして、基本的にはQueryClientProvider
のclient
プロパティに初期化したqueryClient
を一度だけ渡す。
Main.tsx
今回は使っていないが、Main.tsxでは本来React Navigationのルートナビゲーターなどを配置するために使用する。今回はそのままHomeScreen
コンポーネントを配置する。
// Main.tsx import { StatusBar } from "expo-status-bar"; import { HomeScreen } from "./screens/HomeScreen"; export default function Main() { return ( <> <HomeScreen /> <StatusBar style="auto" /> </> ); }
HomeScreen.tsx
では実際にReact Queryを使用するHomeScreen
コンポーネントを作成していく。
import { useQuery } from "@tanstack/react-query"; import { useEffect } from "react"; import { Text, Box, FlatList, VStack, HStack, Image, Spacer, Heading, } from "native-base"; const loader = () => { return { "2022-07-15": [ { id: "activity-id-1", label: "腹筋ローラー", date: "2022/7/15", value: "20", method: "COUNT", exp: 100, createdAt: "14:25", icon: "https://wallpaperaccess.com/full/317501.jpg", }, { id: "activity-id-2", label: "腹筋ローラー", date: "2022/7/15", value: "20", method: "COUNT", exp: 100, createdAt: "14:28", icon: "https://wallpaperaccess.com/full/317501.jpg", }, { id: "activity-id-3", label: "腹筋ローラー", date: "2022/7/15", value: "01:00", method: "TIME", exp: 200, createdAt: "14:31", icon: "https://wallpaperaccess.com/full/317501.jpg", }, ], }; }; export function HomeScreen() { const query = useQuery(["home_screen_loader"], loader); return ( <Box flex={1} safeArea> <Box px={3} py={3}> <Heading>最近のアクティビティ</Heading> </Box> <FlatList contentContainerStyle={{ paddingHorizontal: 12, paddingVertical: 12, }} h="1/2" showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} backgroundColor={"dark.800"} data={query.data?.["2022-07-15"] ?? []} renderItem={({ item }) => { const { icon, date, label, value, createdAt, method, exp } = item; return ( <> <VStack w="full" borderRadius={10} bg={"white"} px={3} py={3}> <HStack mb={3}> <Image source={{ uri: icon, }} alt="Alternate Text" size="sm" borderRadius={10} /> <VStack px={3}> <Text fontWeight={date === "今日" ? "bold" : "normal"}> {date} </Text> <Text color="dark.400">{label}</Text> </VStack> </HStack> <HStack> <VStack> <Text fontWeight={"bold"} fontSize={"lg"}> {value} </Text> <Text color="dark.500"> {method === "COUNT" ? "回" : "時間"} </Text> </VStack> <Spacer /> <VStack> <Text fontWeight={"bold"} fontSize={"lg"}> {exp} </Text> <Text color="dark.500">経験値</Text> </VStack> <Spacer /> <VStack> <Text fontWeight={"bold"} fontSize={"lg"}> {createdAt} </Text> <Text color="dark.500">スケジュール</Text> </VStack> </HStack> </VStack> <Box mb={3} /> </> ); }} /> </Box> ); }
クエリーキー
useQuery
を使用するとき、第一引数にはクエリーキーを指定する。クエリーキーは1つの文字列を含む配列のようなものから、多数の文字列を含んでいたりオブジェクトを含む配列でも可能です。クエリーキーとして認められる唯一の条件はシリアライズ可能であり、クエリーのデータに対してユニークであることです。
クエリー関数
React Queryがデータを要求するために使用される関数です。クエリー関数の第一引数にはQueryFunctionContext
を受け取ります。
解決したデータを返すかエラーをスローするかして必ずプロミスを返さないといけない。undefined
を返すことは許されないので注意が必要。
実行してみる
アプリを起動するとこんな感じに表示される。
これだけだとReact Queryの恩恵がよくわからないかもしれないが、今回で言うとloader
関数の部分でFirebase Functionsの呼び出しやREST APIの呼び出し等を行うことで、表示部分とデータの取得に関わる処理の分離がシンプルに実装できるのでコードの管理がとても楽になる。