Use Query String Instead of useState
Although I titled this post as "Use Query String Instead of useState", you don't need to use query string
in all cases. I provided an example where you may find the usage of query string
useful.
Example
I will implement the example using both useState
and query string
.
To focus on the differences between useState
and query string
later, I will first provide a general explanation of the example code.
Project File Structure
There are two folders in src
. components
and hooks
.
hooks
In the hooks
folder, there are two files.
useAttractions.ts
This API hook is used to fetch data from the server using react-query
library. For the server, json-server is utilized.
import { useQuery } from 'react-query';
export type COUNTRY_CODE = 'de' | 'kr';
export type COUNTRY_CODE_FILTER = 'all' | COUNTRY_CODE;
const useAttractions = ({
country,
keyword,
}: {
country: COUNTRY_CODE_FILTER;
keyword: string;
}) => {
return useQuery<
{
id: number;
country: string;
title: string;
url: string;
}[]
>({
queryKey: ['useAttractions', country, keyword],
queryFn: async () => {
const params: {
title_like: string;
country?: COUNTRY_CODE;
} = {
title_like: keyword,
};
if (country !== 'all') params.country = country;
const res = await fetch(
`http://localhost:3000/attractions?${new URLSearchParams(params)}`
);
return res.json();
},
useErrorBoundary: true,
});
};
export default useAttractions;
useSearchParam.ts
This custom hook is used to extract a specific data from the query string. This is my own implementation, and, there are many good libraries available for manipulating query string
that you can use instead.
import { useSearchParams } from 'react-router-dom';
const useSearchParam = <T extends string>({
key,
defaultValue,
}: {
key: string;
defaultValue?: T;
}): [T, (value: T) => void] => {
const [searchParams, setSearchParams] = useSearchParams();
const setter = (value: T) => {
const newParams = new URLSearchParams(searchParams.toString());
newParams.set(key, value as string);
setSearchParams(newParams);
};
return [(searchParams.get(key) || defaultValue || '') as T, setter];
};
export default useSearchParam;
query string
is essentially a string, so the generic type extends string
. In this code, version 6 of react-router-dom
is being used, and the useSearchParams
hook provided by the library is utilized.
components
In the components
folder, let's see the two files SearchPage.tsx
and AttractionItem.tsx
.
AttractionItem.tsx
interface AttractionProps {
url: string;
country: string;
title: string;
}
const AttractionItem = ({ url, country, title }: AttractionProps) => {
return (
<div className="search-page-result-card">
<img src={url} alt="attraction image" />
<div>
<h5>{country}</h5>
<span>{title}</span>
</div>
</div>
);
};
export default AttractionItem;
It takes three parameters, url
, country
, and title
, and renders the item.
SearchPage.tsx
import { useRef, useState } from 'react';
import useAttractions, { COUNTRY_CODE_FILTER } from '../hooks/useAttractions';
import AttractionItem from './AttractionItem';
import './SearchPage.css';
const SearchPage = () => {
const [country, setCountry] = useState<COUNTRY_CODE_FILTER>('all');
const [keyword, setKeyword] = useState<string>('');
const [countryOptions] = useState<
{
label: string;
country: COUNTRY_CODE_FILTER;
}[]
>([
{ label: 'ALL', country: 'all' },
{ label: 'South Korea', country: 'kr' },
{ label: 'Germany', country: 'de' },
]);
const inputRef = useRef<HTMLInputElement>(null);
const { data } = useAttractions({
country,
keyword,
});
const search = () => {
if (!inputRef.current) {
console.error('inputRef.current is not defined', inputRef);
return;
}
setKeyword(inputRef.current.value);
};
const handleCountryChange = (country: COUNTRY_CODE_FILTER) => () => {
setCountry(country);
};
const handleEnter =
(callback: VoidFunction): React.KeyboardEventHandler<HTMLInputElement> =>
(e) => {
if (e.code === 'Enter') callback();
};
return (
<div className="search-page">
<h1>Attractions</h1>
<div className="search-page-tool">
<input
type="text"
ref={inputRef}
onKeyUp={handleEnter(search)}
defaultValue={keyword}
/>
<button type="button" onClick={search}>
Search
</button>
</div>
<ul className="search-page-filters">
{countryOptions.map((option) => (
<li
key={option.country}
className={country === option.country ? 'active' : ''}
onClick={handleCountryChange(option.country)}
>
{option.label}
</li>
))}
</ul>
<div className="search-page-result">
{data?.map((d) => (
<AttractionItem key={d.id} {...d} />
))}
</div>
</div>
);
};
export default SearchPage;
This is the main page component. You can search a keyword by pressing Enter
.
useState
...
const [country, setCountry] = useState<COUNTRY_CODE_FILTER>('all');
const [keyword, setKeyword] = useState<string>('');
...
As you cab see, it works well. However, if you refresh the page, the data will disappear as shown below.
Every time, you enter this page, it will be empty.
Suppose you want to share the page with your friends. If you use query string
, you can share the page URL with them.
query string
import { useRef, useState } from 'react';
import useAttractions, { COUNTRY_CODE_FILTER } from '../hooks/useAttractions';
import useSearchParam from '../hooks/useSearchParam';
import AttractionItem from './AttractionItem';
import './SearchPage.css';
const SearchPage = () => {
const [country, setCountry] = useSearchParam<COUNTRY_CODE_FILTER>({
key: 'country',
defaultValue: 'all',
});
const [keyword, setKeyword] = useSearchParam<string>({
key: 'keyword',
defaultValue: '',
});
const [countryOptions] = useState<
{
label: string;
country: COUNTRY_CODE_FILTER;
}[]
>([
{ label: 'ALL', country: 'all' },
{ label: 'South Korea', country: 'kr' },
{ label: 'Germany', country: 'de' },
]);
const inputRef = useRef<HTMLInputElement>(null);
const { data } = useAttractions({
country,
keyword,
});
const search = () => {
if (!inputRef.current) {
console.error('inputRef.current is not defined', inputRef);
return;
}
setKeyword(inputRef.current.value);
};
const handleCountryChange = (country: COUNTRY_CODE_FILTER) => () => {
setCountry(country);
};
const handleEnter =
(callback: VoidFunction): React.KeyboardEventHandler<HTMLInputElement> =>
(e) => {
if (e.code === 'Enter') callback();
};
return (
<div className="search-page">
<h1>Attractions</h1>
<div className="search-page-tool">
<input
type="text"
ref={inputRef}
onKeyUp={handleEnter(search)}
defaultValue={keyword}
/>
<button type="button" onClick={search}>
Search
</button>
</div>
<ul className="search-page-filters">
{countryOptions.map((option) => (
<li
key={option.country}
className={country === option.country ? 'active' : ''}
onClick={handleCountryChange(option.country)}
>
{option.label}
</li>
))}
</ul>
<div className="search-page-result">
{data?.map((d) => (
<AttractionItem key={d.id} {...d} />
))}
</div>
</div>
);
};
export default SearchPage;
I have switched from useState
to useSearchParam
, which is a custom hook I made. You can use your own way, but in this example, we will focus on the fact we are using query string
for the data keyword
and category
values.
If you search for a keyword, the url will be updated.
When you click a category, the url will be updated.
After refreshing the page, it will remain the same.
If you share this URL with your friends and they open it, they will see the same page. Additionally, we can modify the URL as we want.
In the URL http://localhost:5173/?keyword=Ber&country=de
, let's change the country from de
to kr
and the keyword from Ber
to han
.
http://localhost:5173/?keyword=han&country=kr
Let's open the link. (Make sure to start the dev server on your own on port 5173
)
You will see the following page. (Oh, I found a typo, it should be 'Hongdae' instead of 'Hangdae', but it's not important, let's move on.)
Conclusion
By using query string
, you can get following benefits:
- Provide different pages by URL.
- If your pages are prepared for SEO, it can provide better SEO.
However, you should avoid using query string
for
- Sensitive information
- Long data
Long data can make your URL much long, which can't look good from users. you can use local/session storages and cookies to store some data temporarily or permanently. Use query string
for the data you want to expose via URL.
That's it! I hope you find it useful.
Happy Coding!
Top comments (0)