데굴데굴

<React> react-router v6에서 path의 id값 검증 후 경로를 이동시키는 법 본문

Programming/React

<React> react-router v6에서 path의 id값 검증 후 경로를 이동시키는 법

aemaaeng 2023. 3. 7. 21:48

1. 문제 상황

2. 근본적인 원인 - 이 페이지에 어떻게 접속하게 되었나

3. 해결법 떠올리기

4. 해결 방법

5. 결과

 

부트캠프에서 했던 프로젝트를 리팩토링하다가 이 문제를 만나게 되었다.

문제 상황

'오픈채팅으로 연락하기' 버튼을 누르면 간혹 이런 페이지로 이동하였다.

근본적인 원인 - 이 페이지에 어떻게 접속하게 되었나

'오픈채팅으로 연락하기' 버튼에 쓰인 window.open()은 앞에 프로토콜까지 붙어있어야 정상적인 링크로 인식한다. (window.location.assign()도 마찬가지이다.)

프로토콜이 붙어있지 않을 때

예를 들어서 내가 현재 localhost:3000에 있다고 가정할 때, 콘솔에 window.open('www.naver.com')를 입력하면 주소창에 이렇게 입력된다.

 

반면 window.open('https://www.naver.com')으로 입력하면 정상적으로 네이버 사이트로 이동한다.
글 등록을 구현할 때 프로토콜이 붙어있지 않은 링크를 일차적으로 걸러줬어야 했는데 그러지 않아서 잘못된 형식의 링크가 함께 등록되어버렸다.

그래서 '오픈채팅으로 연락하기' 버튼을 눌렀을 때 이상한 페이지가 뜨는 상황이 발생했다.

스노우볼처럼 구르고 굴러 문제가 커진 것이다...허허 

해결법 떠올리기

우선, 글을 작성할 때부터 링크를 검증하는 로직을 구성하여 애초에 프로토콜이 없는 링크는 등록하지 못하도록 막는 작업부터 했다. placeholder에도 명시를 해놓고 게시글 등록 버튼을 눌렀을 때 검증 함수가 작동하면서 올바르지 않은 경우 팝업이 뜨도록 했다. (이 글의 요지는 그게 아니므로 자세하게 쓰지는 않겠다..)

 

하지만 이미 등록된 글들 중에 잘못된 오픈채팅 링크가 있는 것이 꽤 있었기에 그 페이지도 다뤄야 했다.

(이 페이지가 더 이상 뜨지 않도록...)

오류가 발생한 경로들은 본래 숫자여야 할 id값의 자리에 문자열이 들어오고 있다는 공통점이 있었다.
따라서 정규표현식으로 id값이 숫자일 경우에만 이동하게 하면 될 것 같아 방법을 찾아보았다.

 

처음에 참고한 영상이고, path 안에 /경로/:id(정규표현식) 형태로 쓰면 된다고 하길래 이대로 해봤지만 작동하지 않았다.

 

 

react-router-regex - CodeSandbox

Differentiate num and non-num param in react-router Route component using regex

codesandbox.io

어쩌다 찾게 된 이 데모에서도 영상에서 봤던 방식을 쓰고 있길래 직접 해봤지만 이상하게도 이 프로젝트에서는 작동하지 않았다.

혹시나 싶어서 이 데모의 package.json을 확인해봤더니 리액트 라우터의 버전이 4였다!

 

아무래도 리액트 라우터 버전이 업그레이드되면서 안되는 것 같아 'react-router v6 regex'로 검색해보니 역시나 스택오버플로우에 누군가가 질문한 글이 있었다.

 

React Router V6 - useRoutes() Path Regex

I know react-router-dom v6 is in beta but I'm playing with it and have a couple of questions about useRoutes() & path & regex. BTW - v6 looks great!! Before in v4 I would do the following. It

stackoverflow.com


스택오버플로우 글의 답변을 타고타고 들어가보니 공식 문서 FAQ에 이에 대한 내용이 있었다.
읽어보면 버전 5까지는 path 문자열 안에 정규표현식이 허용됐었는데 이걸 판별하는 라이브러리 path-to-regexp가 무겁기도 하고, 이 라이브러리가 없어도 충분히 구현할 수 있다고 판단하여 버전 6에서는 사라졌다고 한다.

 

FAQs v6.8.2

FAQs Here are some questions that people commonly have about React Router v6. You might also find what you're looking for in the examples. What happened to withRouter? I need it! This question usually stems from the fact that you're using React class compo

reactrouter.com

해결 방법

공식 문서에서 제안하는 방법은 이렇다.

  • 검증하고자 하는 값을 검증하는 컴포넌트를 만든다.
  • 그 컴포넌트를 <Route>에 연결한다.
  • 컴포넌트 안에서 유효한 경로인지 검증한 후 Not found 페이지를 띄우거나 본래 가고자했던 컴포넌트로 이동시킨다.

1. id가 숫자인지 검증하는 ValidateId 컴포넌트 제작

// ValidateId.js
const ValidateId = () => {
  return <div>유효성 검증 컴포넌트</div>
};

export default ValidateId;

처음에는 이렇게 간단하게만 만들어둔다.

2. App.js<Route>에 해당 컴포넌트 연결하기

// App.js
const App = () => {
  return (
    <BrowserRouter>
      {/* ...rest */}
      <Route path="/shareDetail/:id" element={<ValidateId />} />
      <Route path="/reqDetail/:id" element={<ValidateId />} />
    </BrowserRouter>
  )
}

이제 검증 로직만 추가해주면 된다.

3. 유효한 경로인지 검증하는 로직 구성하기

// ValidateId.js
const ValidateId = () => {
  // id가 숫자인지 아닌지 검증하고 페이지로 넘어간다.
  const { pathname } = useLocation();
  const { id } = useParams();
  const articleId = id.match(/\d+/);
  const endpoint = pathname.split('/')[1]; 

  if (!articleId) {
    // NotFound 페이지로 돌려보낸다.
    return <NotFound />;
  }

  // shareDetail일 때와 reqDetail일 때 분기해주기
  if (endpoint === 'shareDetail') {
    return <CommonDetail endpoint="borrows" id={id} />;
  } else if (endpoint === 'reqDetail') {
    return <CommonDetail endpoint="requests" id={id} />;
  }
};

export default ValidateId;

useParams()id를 받아와 String.prototype.match() 메소드로 id가 숫자인지 확인한다.
숫자가 아니라면 <NotFound /> 컴포넌트를 리턴하여 잘못된 페이지에 접근했음을 사용자에게 알린다.

 

id가 숫자인지만 판단하면 된다면 최종 return문에 바로 가고자 하는 컴포넌트를 작성해주면 된다.

 

이 프로젝트에서는 1️⃣ 접속할 때 id가 필요하고, 2️⃣ 페이지 오류가 발생하는 경로가 shareDetail, reqDetail 이렇게 두 개가 있었기 때문에 해당 페이지로 이동할 수 있도록 분기해줘야 했다.
어떤 페이지로 가는 id인지 판단하기 위해 useLocation()을 이용해 전체 경로를 받아온다.
예를 들어, 경로가 localhost:3000/example/1일 때, useLocation()으로 pathname을 받아오면 localhost:3000 뒤의 경로가 문자열로 출력된다.

/example/1

이를 String.prototype.split()으로 파싱하여 세부 경로를 판단한 후 각 컴포넌트로 연결시켜주면 된다.

 

결과

id값이 숫자가 아닌 문자열로 들어왔을 때에 더 이상 그 페이지가 뜨지 않고 NotFound 페이지로 잘 이동한 것을 볼 수 있다.

 

path 문자열 내부에서 정규표현식 쓰는 방법이 없어져서 처음에는 더 불편해진 거 아니었나 생각했는데 컴포넌트를 따로 만들어 그 안에서 더 세밀하게 검증이 가능해지니까 오히려 좋은 것 같기도 하다.

 

이제 NotFound 컴포넌트 제대로 만들러 가야지🏃‍♀️

Comments