정리 후 팀 노션에 올릴 용도로 정리 😌
주소창 URL 뒤에 ?로 붙는 문자열
// 인기 순 정렬
https://www.example.com/products?sort=popular
// 인기 순 정렬 + 내림차순 정렬
https://www.example.com/products?sort=popular&direction=desc
이동 링크에 쿼리 스트링을 포함해서 전달하면 끝
// Link 컴포넌트 방식
<Link to="/list?sort=popular" />
// useNavigate 방식
navigate("list?sort=popular")
2가지 방법이 있다.
import { useLocation } from 'react-router-dom';
const location = useLocation();
console.log(location.search); // ?sort=popular
그런데 여기서 popular만 뽑아서 사용하면 별도의 작업이 필요해서 복잡하다.
쿼리 페어가 &로 여러 개 있다면 더더욱 그렇다.
따라서 URLSearchParams라는 객체를 활용해서 이 객체에서 제공하는 메서드로 원하는 값을 편하게 가져오자.
URLSearchParams라는 객체를 제공하는 훅이 바로 useSearchParams이다.
import { useSearchParams } from 'react-router-dom';
const [searchParams, setSearchParams] = useSearchParams();
useSearchParams 선언은 useState와 유사하게 한다.
searchParams에 메서드를 붙여 쿼리 스트링 조작을 간편하게 할 수 있다.
searchParams.get(key)
searchParams.getAll(key)
searchParams.toString()
serchParams을 변경하는 메서드로 값을 변경해도 실제 url의 쿼리 스트링은 변경되지 않는다.
변경하려면 setSearchParams에 searchParams를 인자로 전달해야 한다.
import { useSearchParams } from 'react-router-dom';
const [searchParams, setSearchParams] = useSearchParams();
const handleSetParams = () => {
searchParams.set('sort', 'clear');
setSearchParams(searchParams);
}
const handleAppendParams = () => {
searchParams.append('sort', 'hello');
setSearchParams(searchParams);
}
?offset=0&limit=10
// 유저가 /list?offset=10&limit=10 으로 접속한다고 가정
import React, { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
const List = () => {
const [searchParams, setSearchParams] = useSearchParams();
const offset = searchParams.get('offset'); // offset 키의 value 저장 (10)
const limit = searchParams.get('limit'); // limit 키의 value 저장 (10)
return (
<section>
<h1>디스 이즈 포스트</h1>
</section>
);
}
여기에 다음 코드를 추가한다.
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/posts_start=${offset}&_limit=${limit}`)
.then((response) => response.json())
.then((result) => setPosts(result));
}, [offset, limit]);
페이지 번호와 함께 해당 페이지마다 포스트를 10개씩 보여주는 함수 생성
(1번은 1-10번 포스트, 2번은 11-20번 포스트, 3번은 21-30번 포스트 등)
const movePage = (pageNumber) => {
// set으로 searchParams 객체의 offset 키 value를 교체하고
searchParams.set('offset', (pageNumber - 1) * 10);
// 변경된 searchParams를 setSearchParams에 다시 넣어준다. (갱신)
setSearchParams(searchParams);
};
버튼 태그에서 페이지 번호를 함수의 인자로 넣어서 실행
<>
<button onClick={() => movePage(1)}>1 페이지</button>
<button onClick={() => movePage(2)}>2 페이지</button>
<button onClick={() => movePage(3)}>3 페이지</button>
</>
참고하기
- 프론트엔드의 쿼리 스트링과 백엔드에 보내는 쿼리 스트링의 key는 동일하지 않을 수 있다.
- 쿼리 스트링은 동일한 파라미터(path, 리소스)에 추가 정보를 포함한 것
offset과 limit을 컴포넌트의 state가 아닌 쿼리 스트링으로 관리하는 이유?
- 컴포넌트의 state는 화면 이동 시(페이지네이션) 초기화된다.
- 쿼리 스트링은 url에 정보가 담겨 있어 페이지 정보가 유지된다.
=> 필터링, 검색 결과 구현 시 특정 페이지 정보가 유지되어야 하는 경우 state가 아닌 쿼리 스트링 활용하기
useLocation, useParams, useSearchParams로 가져올 수 있는 것?
예시 URL:
https://www.example.com/products?sort=popular
/products?sort=popular
)/products
)?sort=popular
)=> 쿼리 스트링은 key=value로 이루어져 있는데 key와 value를 따로 구할 때 useLocation으로 가져오면 split 메서드를 사용하는 등 복잡하므로 useSearchParams로 가져와서 제공되는 메서드로 편하게 꺼내쓰자 😚
// src/List.js
import React, { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import './List.css';
const List = () => {
const [searchParams, setSearchParams] = useSearchParams();
const offset = searchParams.get('offset');
const limit = searchParams.get('limit');
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_start=${offset}`)
.then((response) => response.json())
.then((result) => setPosts(result));
}, [offset, limit]);
const movePage = (pageNumber) => {
// page 1일 때 offset 0, page 2일 때 offset 10
searchParams.set('offset', (pageNumber - 1) * 10);
setSearchParams(searchParams);
};
return (
<section>
<h1>디스 이즈 포스트</h1>
{posts.map(({ id, title }) => (
<article key={id}>
<p>
<div>id: {id}</div>
<div></div>
</p>
</article>
))}
<div>
<button onClick={() => movePage(1)}>1 페이지</button>
<button onClick={() => movePage(2)}>2 페이지</button>
<button onClick={() => movePage(3)}>3 페이지</button>
</div>
</section>
);
};
export default List;