개발할 때 링크에 복잡한 쿼리스트링 파라미터를 쉽게 관리하는 패턴을 커스텀훅으로 만들었습니다. 기존에 서버사이드 개발 시 요긴하게 사용했던 함수의 방식을 리엑트로 옮긴 것입니다. 링크 URL을 만들 때 그 기능의 맥락만 생각하면 되기 때문에 파라미터 복잡도가 높아졌을 때 유용합니다.
간단히 커스텀훅 코드와 사용 예를 적어두는 기록용으로 올립니다.
사용 예
아래처럼 withSearchParams 함수를 꺼내서 사용합니다.
searchParams는 쿼리스트링 Object입니다.
const { searchParams, withSearchParams } = useSearchParams();
withSearchParams는 인자로 url 와 options 를 넘겨 실행합니다.
옵션없이 적용하면 현재 쿼리스트링을 그대로 적용해줍니다.
// 현재 URI : /abc?page=2&searchKeyword=foo <Link to={withSearchParams(`/articles/${id}/detail`)}> {subject} </Link> // 결과 <Link to={`/articles/${id}/detail?page=2&searchKeyword=foo`}> {subject} </Link>
새로 파라미터를 설정할 때는 set 옵션을 사용합니다.
기존에 존재하는 키는 값만 변경합니다.
// 현재 URI : /articles?foo=1&bar=2 withSearchParams(`/articles`, { set: { page: 1 } }); // 페이지 설정(추가) : /articles?foo=1&bar2&page=1 // 현재 URI : /articles?foo=1&bar=2&page=1 withSearchParams(`/articles`, { set: { page: 2 } }); // 페이지 설정(변경) : /articles?&foo=1&bar2&page=2
일부 파라미터를 제거할 때는 remove 옵션을 사용합니다.
제거할 키값을 배열로 나열합니다.
// 현재 URI : /articles?foo=1&bar=2&page=2 withSearchParams('/articles', { remove: ['foo','bar'] }); // 결과 : /articles?page=2
set, remove를 함께 사용할 경우도 있습니다.
// 현재 URI : /articles?page=4&searchKeyword=foo withSearchParams('/articles', { set: { filter: 'a' }, remove: ['page'] }); // 결과 : /articles?searchKeyword=foo&filter=a
모든 파라미터를 제거 하고 새로 설정할 경우입니다.
// 현재 URI : /articles?searchKeyword=foo&filter=a withSearchParams('/some', { set: { foo: 1 }, skipAll: true }); // 결과 : /some?foo=1
uri 문자열에 이미 쿼리스트링이 포함되어 있는 경우도 처리합니다.
// 현재 URI : /articles?baz=3&page=1 withSearchParams('/some?foo=1&bar=2', { set: { page: 5 }, remove: ['foo'] }); // 결과 : /some?baz=3&page=5&bar=2
useSearchParams.js
import { useState, useCallback, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import queryString from 'query-string'; function getSearchParams(parsedSearchParams = {}, options = {}) { const params = Object.keys(options).reduce((params, optionType) => { if (optionType === 'set') { if (options.skipAll) { return options.set; } return { ...params, ...options.set }; } if (optionType === 'remove') { return Object.keys(params).reduce((obj, key) => { if (options.remove.includes(key) === false) { obj[key] = params[key]; } return obj; }, {}); } return params; }, parsedSearchParams); return queryString.stringify(params, { skipEmptyString: options?.skipEmpty, skipNull: options?.skipEmpty, }); } function useSearchParams() { const { search } = useLocation(); const [searchParams, setSearchParams] = useState(queryString.parse(search)); useEffect(() => { setSearchParams(queryString.parse(search)); }, [search]); const withSearchParams = useCallback( (uri, options) => { const { url, query, fragmentIdentifier } = queryString.parseUrl(uri, { parseFragmentIdentifier: true, }); const newQuery = getSearchParams({ ...searchParams, ...query }, options); return `${url}${newQuery ? `?${newQuery}` : ''}${ fragmentIdentifier ? `#${fragmentIdentifier}` : '' }`; }, [searchParams], ); return { searchParams, setSearchParams, getSearchParams, withSearchParams, }; } export default useSearchParams;