React TS: Use Query String Instead of useState
Tags: react, webdev, beginners, typescript
Published: 2023-03-08 07:44:28

> 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

![main page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14g35p0i9si7ccmqrham.png)


![category selection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6s8tbue6yw4638p1rvrr.png)


![search items](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nx0g1ci37zyfwxvphxon.png)

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

![Project File Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k5ojza2eucqivopymasw.png)

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](https://github.com/typicode/json-server) is utilized.

```typescript
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.

```typescript
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`

```typescript
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`

```typescript
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

```typescript
...
  const [country, setCountry] = useState<COUNTRY_CODE_FILTER>('all');
  const [keyword, setKeyword] = useState<string>('');
...
```

![example using useState, it works well](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x5xqp3pva0ct9n1k3dtg.png)

As you cab see, it works well. However, if you refresh the page, the data will disappear as shown below.

![after refreshing, all data has gone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vn72tnnswizv6fhqyuh.png)

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

```typescript
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.

![keyword is changed by searching](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01sxtsqsfmxl1fc4bi7s.png)

When you click a category, the url will be updated.

![category selected](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z2rtdc5zf3p0by0x109m.png)

After refreshing the page, it will remain the same.

![The looking of the page after refreshing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vgjqukwrcedpi6uvtt7z.png)

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.)

![opened link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/der18pef6hvitmc9mzy2.png)

---

## 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! 

---

[Github Source](https://github.com/hsk-kr/use-qs)