리액트

리액트 react-router-dom v6 react context 밖에서 history.push 하기

ImNM 2022. 2. 3. 18:45

react-router-dom v6로 업그레이드 되면서 switch 가 routes로 바뀌고... 뭐든 옛날 소스를 절대로 가져다 쓸수 없는 상황이 되었다.
가장 불편했던점은 공식문서나 어디에서도 history 객체를 전역으로 가져와서 액션이든 어디든 react element 밖에서 쓸 수 없던 상황이였다. 물론 action에 callback으로 navigate를 넘겨서 처리하는 방법이있지만, 그건 뷰에 로직이 포함되 너무 깊게들어가버리는 상황이다.

이글을 통해 얻을 수 있는 점

  • react-router-dom v6 에서 history 객체를 전역으로 선언하는 방법
  • react-router-dom v6 에서 histroy 객체를 전역으로 받아 push 하는 방법

history


GitHub - remix-run/history: Manage session history with JavaScript

Manage session history with JavaScript. Contribute to remix-run/history development by creating an account on GitHub.

github.com

오늘도 공식문서를 가져와봤다. react-router-dom 은 history 패키지로 히스토리를 관리한다. 스택처럼 이전 url , 이후 url 등을 이렇게 쌓아 여러분들이 새로고침을 안해도 뒤로가기버튼을 눌러서 웹페이지가 재 랜더링 안되며, redux 의 스테이트를 지킬 수 있는 것이다.
history 의 종류는 크게 세가지다

history 종류

이에 맞춰서 react-router-dom 의 종류도 정해진다. 들어가서 확인해 보시라

React Router | API Reference

Declarative routing for React apps at any scale

reactrouter.com




기본적으로 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를 전역으로 생성해서 접근하는 방법이 막히게 되었다.

해결방법


[Bug]: How to navigate outside React context in v6? · Issue #8264 · remix-run/react-router

What version of React Router are you using? v6 Steps to Reproduce In v6 docs, it mentions that we can use useNavigate() hook to do navigation, similar to in v5 we directly use useHistory() hook. Ho...

github.com

간단하다.

react router dom 의 라우터 타입 정의 파일

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를 할 수 있게 된다.