리액트 react-router-dom v6 react context 밖에서 history.push 하기
react-router-dom v6로 업그레이드 되면서 switch 가 routes로 바뀌고... 뭐든 옛날 소스를 절대로 가져다 쓸수 없는 상황이 되었다.
가장 불편했던점은 공식문서나 어디에서도 history 객체를 전역으로 가져와서 액션이든 어디든 react element 밖에서 쓸 수 없던 상황이였다. 물론 action에 callback으로 navigate를 넘겨서 처리하는 방법이있지만, 그건 뷰에 로직이 포함되 너무 깊게들어가버리는 상황이다.
이글을 통해 얻을 수 있는 점
- react-router-dom v6 에서 history 객체를 전역으로 선언하는 방법
- react-router-dom v6 에서 histroy 객체를 전역으로 받아 push 하는 방법
history
오늘도 공식문서를 가져와봤다. react-router-dom 은 history 패키지로 히스토리를 관리한다. 스택처럼 이전 url , 이후 url 등을 이렇게 쌓아 여러분들이 새로고침을 안해도 뒤로가기버튼을 눌러서 웹페이지가 재 랜더링 안되며, redux 의 스테이트를 지킬 수 있는 것이다.
history 의 종류는 크게 세가지다
이에 맞춰서 react-router-dom 의 종류도 정해진다. 들어가서 확인해 보시라
기본적으로 BrowserRouter을 쓰게 될것이다.
import * as React from "react"; import * as ReactDOM from "react-dom"; import { BrowserRouter ,Routes , Route } from "react-router-dom"; ReactDOM.render( <BrowserRouter> {/* The rest of your app goes here */} <Routes> <Route .../> </Routes> </BrowserRouter>, root );
import { useNavigate } from "react-router-dom"; function MainButton() { const navigate = useNavigate(); function handleClick() { navigate("/main"); } return ( <div> <button onClick={handleClick}>main button</button> </div> ); }
위와같은 형식으로 하면 리액트 엘리먼트안에서 새로고침을 하지 않아도 라우트를 옮겨 다닐 수가 있는것이다.
전역으로 접근하기
문제는 리액트 엘리먼트 밖에서 접근을 할려면 어떻게 해야하는 가 이다. 액션에서 axios 요청이 success가 나면 다른 라우트로 푸시를 해주고 싶은 경우가 있다. 이를 위해선
react router dom v5 이전 버젼에서는 history 객체를 공식 다큐멘트처럼
import { createBrowserHistory } from 'history'; export default history = createBrowserHistory();
위와같이 히스토리객체를 선언해서 내 파일안에 집어넣어서 가령 (history.js)
import { Router, Route, Link } from 'react-router-dom'; import history from './history'; ReactDOM.render( <Provider store={store}> <Router history={history}> <div> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/login">Login</Link></li> </ul> <Route exact path="/" component={HomePage} /> <Route path="/login" component={LoginPage} /> </div> </Router> </Provider>, document.getElementById('root'), );
위와같이 index.js 의 라우터에 히스토리 객체를 집어넣으면 되었었다.
하지만 react router dom v6 부터는 Router 자체에 history를 전역으로 생성해서 접근하는 방법이 막히게 되었다.
해결방법
간단하다.
BrowserRouter 의 인자로 history를 받지 않는 것이 보이는가? 반대로 HistoryRouter는 history를 인자로 받고있다. 위의 전역으로 접근하기 방식을 이용해서 history 객체를 선언한후 싱글톤이니 어디서나 접근해도 하나의 객체일 것이고, 그 객체를 History Router로 전달해주면 되는것이다. 이름이 근데 참 애매하다. 권장하지 않는 느낌이기도한데, unstabe_HistoryRouter로 받으면된다.
import history from './history'; function App() { return ( <HistoryRouter history={history}> // The rest of your app </HistoryRouter> ); }
주의점은 history는 여러분이 직접 history.js 라는 파일을 만들어 createBrowserHistroy 로 히스토리를 생성에 줘야한다는 것이다.
위와같이 설정하게 되면,
import history from '../../history';
export const messageValidation =
({ messageToken, authenticationNumber }, nextUrl) =>
async dispatch => {
try {
dispatch({ type: MESSAGE_VALIDATION_PENDING });
const response = await axios.post('/auth/validation', {
messageToken,
authenticationNumber: authenticationNumber
});
dispatch({
type: MESSAGE_VALIDATION_SUCCESS,
payload: response.data.data
});
history.push(nextUrl);
} catch (e) {
//400 ~
console.log(e.response.data);
dispatch({ type: MESSAGE_VALIDATION_ERROR, payload: '인증 오류' });
}
};
위와 같이 history 객체를 임포트해와 리액트 엘리먼트 밖에서도 history.push를 할 수 있게 된다.