React Router v6에서 도입된 새로운 개념으로, 중첩된 라우팅 구조를 구성할 수 있도록 해주는 컴포넌트이다. Outlet 컴포넌트는 라우팅 컴포넌트 내부에서 사용되며, 중첩된 자식 라우트가 렌더링되는 위치를 지정한다.
v6에서 중첩 라우팅이 컴포넌트의 children과 같은 개념으로 중첩이 가능해졌고, 더 직관적으로 변했다.
member 라우트 하위에 :memberId 가 포함된 Route를 추가하면 중첩 라우팅이 된다. 하위에 있으면 자동으로 / 로 구분되기 떄문에 추가적인 / 를 path에 추가할 필요가 없어졌다.
//v5
<Switch>
<Route path="/member" />
<Route path="/member/:memberId" />
</Switch>
//v6
<Routes>
<Route path="/member">
<Route path=":memberId" /> // /member/:memberId
</Route>
</Routes>
router.jsx
레이아웃
{
path: 'auth',
element: <LoginPage />,
children: [
{
path: 'login',
element: <LoginForm />
},
{
path: 'signup',
element: <SignUpForm />
}
]
}
LoginPage.jsx
에서 Outlet을 통해 자식 컴포넌트 레이아웃화해주기
import { Outlet } from 'react-router-dom';
const LoginPage = () => {
return (
<>
<Outlet />
</>
);
};
export default LoginPage;
로그인/회원가입 기능을 구현한 이후에 유효성 검사를 추가하는 과정에서 발생한 문제들과 해결과정을 적어보려고 한다.
회원가입 시 유효성 검사
(1) 이미 회원가입한 유저인 경우
(2) 비밀번호를 형식에 맞지 않게 작성한 경우
(3) 이메일 형식에 맞지 않는 경우로그인 시 유효성 검사
(1) 유저 이메일 맞지 않는 경우
(2) 유저 비밀번호 맞지 않는 경우
처음에는 데이터베이스에 저장된 username과 password를 인풋 값이랑 비교하는 방식으로 유효성처리를 하려고 했고 때문에 데이터베이스에 저장된 username과 password를 어떻게 가져와야하는지 고민했다.
하지만 팀원분께서 해당 방식이 아니라 error message 종류에 따라서 다른 오류 메시지 값을 반환해주는 함수를 작성하는 것을 제안해주셔서 새로운 방식을 사용하게 됐다.
getUserErrorMessage()
: Supabase Auth Error Message를 받고 원하는 오류를 전달해줄 수 있는 string을 반환해주는 함수
export const getUserErrorMessage = (error) => {
if (error.includes('User already registered')) {
return '중복된 이메일입니다.';
} else if (error.includes('duplicate key value violates unique constraint "unique_nickname"')) {
return '중복된 닉네임입니다.';
} else if (error.includes('Unable to validate email address: invalid format')) {
return '이메일 형식에 맞게 제출해주세요.';
} else if (error.includes('Anonymous sign-ins are disabled')) {
return '입력창에 내용을 모두 입력해주세요.';
} else if (error.includes('Password should be at least 6 characters.')) {
return '비밀번호는 최소 6글자 이상 입력해주세요.';
} else {
return '알 수 없는 에러입니다. 다시 시도해주세요.';
}
};
SignUpForm 컴포넌트
안에서 {auth.error && <StErrorMsg>{getUserErrorMessage(auth.error)}</StErrorMsg>}
해당 코드를 사용하여 auth.error가 있을 경우 에러 메시지를 보여주는 방식으로 기능을 구현했다.
return (
<StFormWrapper onSubmit={handleSignUp}>
<StLoginH2>Welcome!</StLoginH2>
<StInputBox>
<StInputImg src={loginPerson}></StInputImg>
<StInputField
type="text"
placeholder="username"
value={nickname}
onChange={(e) => {
setNickname(e.target.value);
}}
/>
</StInputBox>
<StInputBox>
<StInputImg src={loginEmail}></StInputImg>
<StInputField type="email" placeholder="e-mail" value={email} onChange={(e) => setEmail(e.target.value)} />
</StInputBox>
<StInputBox>
<StInputImg src={loginPassword}></StInputImg>
<StInputField type="password" placeholder="password" value={password} onChange={handleChangePassword} />
</StInputBox>
{error && <StErrorMsg>{error}</StErrorMsg>}
{auth.error && <StErrorMsg>{getUserErrorMessage(auth.error)}</StErrorMsg>}
<StLoginBtn>Sign Up</StLoginBtn>
</StFormWrapper>
);
다만, 로그인폼에서는 확인할 수 있는 오류 메시지가 1개 뿐이라 패스워드와 아이디가 각각 다를 경우를 나눠서 유효성 검사를 실행하지는 못했다.
export const getUserLoginErrorMessage = (error) => {
if (error.includes('Invalid login credentials')) {
return '잘못된 이메일과 비밀번호를 입력했습니다. 다시 입력해주세요.';
}
위의 방식이 최선인지는 잘 모르겠지만, 내일 튜터님께 처음에 생각한 방식대로 확인하는 방법을 여쭤보고 다른 방식으로 유효성 검사를 할 수 있을지 알아보려고 한다.
supabase에서 공식 페이지에서 별도로 정리된 오류 메시지는 없었지만, 검색 해보니 오류 메시지를 정리해둔 내용이 있어서 아래 공유해본다. 추후에 필요한 에러 메시지가 있다면 참고하면 좋을 것 같다.
// * Supabase Auth Error Message Translation
useEffect(() => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type !== "childList" || mutation.addedNodes.length === 0)
return;
for (const node of mutation.addedNodes) {
if (
node instanceof HTMLElement &&
(node.classList.contains("supabase-account-ui_ui-message") ||
node.classList.contains("supabase-auth-ui_ui-message"))
) {
const originErrorMessage = node.innerHTML.trim();
let translatedErrorMessage = "<DEFAULT MESSAGE>";
switch (originErrorMessage) {
case "To signup, please provide your email":
translatedErrorMessage = "";
break;
case "Signup requires a valid password":
translatedErrorMessage = "";
break;
case "User already registered":
translatedErrorMessage = "";
break;
case "Only an email address or phone number should be provided on signup.":
translatedErrorMessage = "";
break;
case "Signups not allowed for this instance":
translatedErrorMessage = "";
break;
case "Email signups are disabled":
translatedErrorMessage = "";
break;
case "Email link is invalid or has expired":
translatedErrorMessage = "";
break;
case "Token has expired or is invalid":
translatedErrorMessage = "";
break;
case "The new email address provided is invalid":
translatedErrorMessage = "";
break;
case "Password should be at least 6 characters":
translatedErrorMessage = "";
break;
case "Invalid login credentials":
translatedErrorMessage = "";
break;
}
if (!document.querySelector("#auth-forgot-password")) {
node.innerHTML = translatedErrorMessage;
}
}
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
}, []);