はじめに
表題通り、ReactHookFormでクエリパラメータを更新し、クエリパラメータの変更を検知する事でAPI通信する仕組みを作ります。やりたい事は下記の図の通りです。
成果物
ソースコード
src/App.tsx
import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { Container, Typography } from '@mui/material';
import { JsonPlaceHolder } from './components'
const App: React.FC = () => {
return (
<Router>
<Container>
<Typography variant="h4" component="h1" gutterBottom>
Search Example with React Hook Form
</Typography>
<Routes>
<Route path="/" element={<JsonPlaceHolder />} />
</Routes>
</Container>
</Router>
);
};
export default App;
src/components/index.tsx
import { useQueryParams, useFetchData } from "./hooks"
import SearchForm from "./SearchForm"
import { SearchResult } from "./SearchResult"
export const JsonPlaceHolder = () => {
const query = useQueryParams();
const { results, loading } = useFetchData(query);
return (
<div>
<h1>JsonPlaceHolder</h1>
<SearchForm />
<SearchResult results={results} loading={loading} />
</div>
);
};
src/components/SearchForm.tsx
import { useForm } from 'react-hook-form';
import { TextField, Button, Box } from '@mui/material';
import { useLocation, useNavigate } from 'react-router-dom';
import queryString from 'query-string';
type SearchFormData = {
query: string;
};
const SearchForm = () => {
const location = useLocation();
const navigate = useNavigate();
const params = queryString.parse(location.search);
const defaultQuery = params.query as string || '';
const { register, handleSubmit } = useForm<SearchFormData>({
defaultValues: { query: defaultQuery }
});
const onSubmit = (data: SearchFormData) => {
const newQuery = queryString.stringify({ query: data.query });
navigate(`?${newQuery}`);
};
return (
<Box>
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
label="Search"
{...register('query')}
variant="outlined"
fullWidth
margin="normal"
/>
<Button type="submit" variant="contained" color="primary">
Search
</Button>
</form>
</Box>
);
};
export default SearchForm;
src/components/SearchResult.tsx
import { Box, CircularProgress, List, ListItem, ListItemText } from '@mui/material';
type SearchResultProps = {
results: any[];
loading: boolean;
};
export const SearchResult = ({ results,loading}:SearchResultProps) => {
return (
<Box mt={4}>
{loading ? (
<Box display="flex" justifyContent="center">
<CircularProgress />
</Box>
) : (
<List>
{results.map((result) => (
<ListItem key={result.id}>
<ListItemText primary={result.title} />
</ListItem>
))}
</List>
)}
</Box>
);
};
hooks.tsx
import { useLocation } from "react-router-dom";
import { useState, useEffect } from "react";
import queryString from "query-string";
import axios from "axios";
export const useQueryParams = () => {
const location = useLocation();
const params = queryString.parse(location.search);
const queryParam = params.query as string || '';
return queryParam;
};
export const useFetchData = (query: string) => {
const [results, setResults] = useState<any[]>([]);
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts', {
params: { q: query }
});
setResults(response.data);
} catch (error) {
console.error('Error fetching data:', error);
setResults([]);
} finally {
setLoading(false);
}
};
if (query) {
fetchData();
} else {
setResults([]);
}
}, [query]);
return { results, loading };
};