웹브라우저의 기본 경험을 React SPA에서 구현할 때 사용하는 간단한 훅입니다.
import { useState, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; const useHistoryState = (initialState, key) => { const history = useHistory(); const stateValue = history.location.state?.[key]; const [historyState, setHistoryState] = useState( stateValue === undefined ? initialState : stateValue, ); const setState = useCallback( (state, replace = false) => { const value = state instanceof Function ? state(historyState) : state; setHistoryState(() => value); history.replace({ state: replace ? value : { ...history.location.state, [key]: value }, }); }, [history, historyState, key], ); return [historyState, setState]; }; export default useHistoryState;
아래는 Next.js에서의 useHistoryState.js 코드입니다.
Next.js에서는 SSR과 CSR의 렌더링 차이에 대한 우려로 Router에 state를 추가할 수 있는 기능을 제공하지 않는데요. 브라우저 기본 경험처럼 구현해야 한다는 점이 우선이라 판단해서 아래처럼 꼼수 적용하는 방법을 추가했습니다.
import { useState, useCallback } from 'react'; const historyStorage = (history => { history.replaceState = (replaceState => (state = {}, title, url) => replaceState.call(history, { ...history.state, ...state }, title, url))( history.replaceState, ); const get = key => history.state?.page?.[key]; const set = (key, value, replace = false) => { history.replaceState({ page: replace ? { [key]: value } : { ...history.state?.page, [key]: value }, }); }; return { set, get }; })(typeof window !== 'undefined' ? window.history : {}); const useHistoryState = (initialState, key) => { const stateValue = historyStorage.get(key); const [historyState, setHistoryState] = useState( stateValue === undefined ? initialState : stateValue, ); const setState = useCallback( (state, replace = false) => { const value = state instanceof Function ? state(historyState) : state; setHistoryState(() => value); historyStorage.set(key, value, replace); }, [historyState, key], ); return [historyState, setState]; }; export default useHistoryState;
아래처럼 setState와 유사하게 사용합니다.
// useHistoryState(기본값, 키이름) const [sort, setSort] = useHistoryState('desc', 'sort'); const [showPanel, setShowPanel] = useHistoryState(false, 'showPanel'); // or const [pageForm, setPageForm] = useHistoryState({ title: '', content: '', tags: [], }, 'pageForm'); return ( <> <select value={sort} onChange={({ target }) => setSort(() => target.value)}> <option value="desc">내림차순</option> <option value="acs">오름차순</option> </select> <label> <input type="checkbox" checked={showPanel} onChange={({ target }) => setShowPanel(() => target.checked)} /> 패널 표시 </label> </> );
이렇게 뒤로가기/앞으로가기, 새로고침 시 input에 입력한 값을 유지시켜서 브라우저 기본 동작처럼 자연스럽게 동작하도록 구현할 때 사용합니다. 그리고 SWR과 조합하여 뒤로가기 시 캐시를 활용하고 API 요청을 하지 않도록 처리합니다.
안녕하세요?
저는 인공지능 스타트업에 다니는 정승환이라고 합니다.
블로그의 내용들 정말 잘 보았습니다. 소중한 지식의 나눔, 감사드립니다.
관련해서 간단하게 제안드릴 내용(온라인 강의)이 있어서
메일로 연락드리고 싶습니다.
정말 바쁘시겠지만,
메일 한 번 이야기 나누어 볼 수 있을까요?
제 메일은
hwan@lionrocket.ai
입니다. 회신주시면, 제안내용과 함께 꼭 설명드리고 싶습니다.
좋은 하루 되세요 🙂
정승환 드림