I have a next.js project with react-query. Basically what I need to do is display all the countries from the API on the homepage, but there's also an input to search by name, and a select to search by region. How can i change what query to call based on that?
I thought of saving the data to a useState and then displaying it but that seems like a waste of react-query's potential. Here's how I'm organizing my code:
countries.service.ts
import { Regions } from '@/queries/types';
import axios from 'axios';
export class CountriesService {
static client = axios.create({
baseURL: `${process.env.NEXT_PUBLIC_API_URL}`,
});
static async getAllCountries() {
const response = await CountriesService.client.get('/all');
return response.data;
}
static async getCountryByName(name: string) {
const response = await CountriesService.client.get(`/name/${name}`);
return response.data;
}
static async getCountryByRegion(region: Regions) {
const response = await CountriesService.client.get(`/region/${region}`);
return response.data;
}
}
countries.query.ts
import { useQuery } from '@tanstack/react-query';
import { CountriesService } from '@/services/countries.service';
import { Regions } from './types';
export class CountriesQuery {
static getAllCountries() {
return useQuery({
queryKey: ['get-all-countries'],
queryFn: async () => {
const data = await CountriesService.getAllCountries();
return data;
},
});
}
static getCountryByName({ name }: { name: string }) {
return useQuery({
queryKey: ['get-country-by-name'],
queryFn: async () => {
const data = await CountriesService.getCountryByName(name);
return data;
},
});
}
static getCountryByRegion({ region }: { region: Regions }) {
return useQuery({
queryKey: ['get-country-by-region'],
queryFn: async () => {
const data = await CountriesService.getCountryByName(region);
return data;
},
});
}
}
page.tsx
'use client';
import { CountryCard } from "@/components/country-card";
import { CountriesQuery } from "@/queries/countries.query";
import { Country } from "@/queries/types";
import { MagnifyingGlass } from "@phosphor-icons/react";
import { useState } from "react";
export default function Home() {
const [searchName, setSearchName] = useState('');
const { data, isLoading} = CountriesQuery.getAllCountries();
const handleSearchByName = async (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchName(e.target.value);
};
if (isLoading) {
//make skeleton
return;
};
return (
<main>
<div>
<input onChange={handleSearchByName} placeholder="Search for a country..." />
<button><MagnifyingGlass weight="bold" /></button>
</div>
<div>
{data.map((country: Country) => (
<div key={country.name.common} >
<CountryCard
flagSrc={country.flags.png}
name={country.name.common}
population={country.population}
region={country.region}
capital={country.capital}
/>
</div>
))}
</div>
</main>
)
}
You've over-specified your custom query hooks (and also violated the rules of hooks).
The correct way to implement this as a custom hook would be something like:
export const useCountryQuery({ name, region, ...options } = {}) {
return useQuery({
queryKey: ['get-country', {name, region}]}
queryFn: () => {
if(name) {
return CountriesService.getCountryByName(name);
}
if(region) {
return CountriesService.getCountryByRegion(region);
}
return CountriesService.getAllCountries();
},
...options
})
}
To be used like:
const [searchName, setSearchName] = useState('');
const { data, isLoading } = useCountryQuery({name: searchName});
const handleSearchByName = async (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchName(e.target.value);
};
// ...