웹브라우저의 기본 경험을 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 요청을 하지 않도록 처리합니다.