간단한 화면 구성하기
로그인 폼을 만들기 위해, 지금부터 MUI로 간단한 화면 구성을 먼저 만들어둘 예정이다. 이때, 모든 페이지는 상단에 bar가 있어야 한다. 모든 페이지에 일일이 bar 컴포넌트를 넣는 대신, 하나의 레이아웃 형태로 모든 페이지가 동일한 bar를 가지고 있을 수는 없을까?
리액트에서도 그랬듯, Next.js에서도 마찬가지로 최상위 파일에 컴포넌트를 지정하고 children을 통해 하위 컴포넌트를 랜더링하면 굳이 모든 페이지에 bar를 지정할 필요 없이 공통적으로 사용할 수 있게 된다.
현재 Next.js의 app 폴더는 다음과 같은 구조로 되어있다.
app
├── favicon.ico
├── globals.css
├── layout.tsx
├── login
│ └── page.tsx
├── page.module.css
└── page.tsx
이때 app 하위의 page.tsx는 https://localhost:3000/ 경로에 해당하는 Home에 해당하는 컴포넌트이다. url을 통해 접근할 수 있다. 이러한 page들이 공유하는 헤더나 푸터 등 공유되는 UI는 layout.tsx에 지정하여 사용할 수 있다.
MUI App Bar
App Bar React component - Material UI
The App Bar displays information and actions relating to the current screen.
mui.com
MUI에서 제공하는 컴포넌트 중, Basic App Bar을 layout.tsx에 사용하여 하위 컴포넌트들이 App Bar을 공유할 수 있도록 해볼 것이다. 먼저 App Bar 컴포넌트를 src/components 폴더 하위에 별도로 폴더를 생성하여 만들어준다.
// src/components/Header
import {
AppBar,
Box,
Toolbar,
Typography,
Button,
IconButton,
} from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
const Header = () => {
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
News
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
</Box>
);
};
export default Header;
이제 해당 컴포넌트가 모든 페이지에 적용될 수 있도록 layout.tsx에 배치한다.
Header가 제대로 최상단에 배치될 수 있도록 body 내부에 넣어주었다.
"use client";
import { useEffect } from "react";
import { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { setupWorker } from "msw";
import { handlers } from "@/mocks/handlers";
import Header from "@/components/Header";
const inter = Inter({ subsets: ["latin"] });
// export const metadata: Metadata = {
// title: 'Create Next App',
// description: 'Generated by create next app',
// };
const RootLayout = ({ children }: { children: React.ReactNode }) => {
// msw mocking
useEffect(() => {
if (typeof window !== "undefined") {
const worker = setupWorker(...handlers);
worker.start();
}
}, []);
return (
<html lang="en">
<body className={inter.className}>
<Header />
<div>{children}</div>
</body>
</html>
);
};
export default RootLayout;
위와 같이 진행하면 아래와 같은 결과가 나타날 것이다.

Login 페이지를 추가할 준비가 끝났다. 이제 기존에 만들어뒀던 /login 페이지가 Login을 클릭할 경우 이동할 수 있게 만들면 된다. 페이지 전환을 위해 Link 태그를 사용하여 아래와 같이 Header 컴포넌트를 수정한다.
<Button color="inherit">
<Link href="/login">Login</Link>
</Button>
이제 로그인 폼을 만들 준비가 끝났다. 본격적으로 React-Hook-Form을 사용해보자!
React-Hook-Form
React Hook Form 라이브러리는 이름과 같이 form에서 React Hook을 사용할 수 있게 하는 기능을 제공한다. 우선은 간단한 형태로 Login 컴포넌트를 만들어보도록 하자.
기존에 Hello World를 출력하던 Login 컴포넌트를 다음과 같이 바꾼다.
"use client";
import { useEffect } from "react";
import { useForm, SubmitHandler } from "react-hook-form"; // 폼 생성을 위한 import
import { TextField, Button } from "@mui/material"; // MUI 라이브러리
// Form에 입력된 데이터를 받을 Inputs 인터페이스 지정
interface Inputs {
id: string;
password: string;
}
const Login = () => {
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm<Inputs>();
const onSubmit: SubmitHandler<Inputs> = (data) => {
console.log("login proceed", data);
};
const watchID = watch("id");
useEffect(() => {
console.log(watchID); // watch를 통해 "example"에 데이터가 전달되는지 체크
}, [watchID]);
return (
/* "handleSubmit"이 onSubmit 동작 되기 전에 inputs을 식별 */
<form onSubmit={handleSubmit(onSubmit)}>
{/* "register" 함수로 example 변수에 데이터 저장
MUI 텍스트필드 사용
*/}
<TextField
defaultValue=""
{...register("id")}
variant="outlined"
label="ID"
fullWidth
margin="normal"
/>
{/* 추가적인 제약조건 지정 (반드시 데이터를 입력해야 하는 폼) */}
<TextField
{...register("password", { required: true })}
variant="outlined"
label="Password"
type="password"
fullWidth
margin="normal"
error={Boolean(errors.password)}
helperText={errors.password && "This field is required"}
/>
{/* MUI 버튼 사용 */}
<Button type="submit" variant="contained" color="primary">
SUBMIT
</Button>
</form>
);
};
export default Login;
만약 본인이
nextjs Error: (0 , react_hook_form__WEBPACK_IMPORTED_MODULE_1__.useForm) is not a function
와 같은 에러가 발생한다면, "use client"가 누락되어 발생하고 있는 문제일 가능성이 크다.
추가적으로 브라우저에 다크 테마를 적용하고 있을 경우, global.css가 다크 테마로 지정되어 폼이 제대로 보이지 않을 수 있다. global.css를 수정하여 폼이 잘 보일 수 있도록 하였다.
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
a {
color: inherit;
text-decoration: none;
}
여기까지 진행하였다면 다음과 같은 화면이 보일 것이다.

React-Hook-Form 기능에 대하여
현재 로그인 폼에 사용된 라이브러리 기능들은 다음과 같다.
- register
- watch
- handleSubmit
- formstate
이는 form을 만들기 위해 사용되는 아주 기본적인 기능들이며, 이외에도 getValue나 reset등 유용한 기능들이 더 존재한다. 더 자세한 기능들에 대해서는 필자는 아래의 글을 참고하였다.
https://tech.osci.kr/introduce-react-hook-form/
react-hook-form 을 활용해 효과적으로 폼 관리하기 - 오픈소스컨설팅 테크블로그 %
오픈소스컨설팅 테크블로그 react-hook-form 을 활용해 효과적으로 폼 관리하기 오픈소스컨설팅에서 프론트엔드 개발을 하고 있는 강동희입니다. react-hook-form 을 도입한 경험을 공유합니다!
tech.osci.kr
여기서는 우선 위의 네 가지에 대해서만 다룰 예정이다.
1. register
form 내부에 있는 input에 react-hook-form을 사용할 것이라 등록(register) 하기 위해 사용된다.
{...register("id")}
TextField 내부에 다음과 같이 id input을 등록할 수 있다.
2. watch
특정 input에 대한 변화를 탐지할 수 있는 기능이다. 해당 기능을 활용하여 input에 입력되는 값의 변화를 확인할 수 있다.
const watchID = watch("id");
useEffect(() => { console.log(watchID); // watch를 통해 데이터가 전달되는지 체크 }, [watchID]);
이와 같이 코딩했을 때, console.log로 다음과 같이 확인할 수 있다.

3. handleSubmit
두 개의 인자를 받을 수 있는 함수다. 필수 인자로 받는 첫 번째 함수는 form이 성공했을 때 실행되는 것이며, 두 번째 인자는 실패했을 때 실행된다. 두 번째 인자는 필수가 아니다.
const onSubmit: SubmitHandler<Inputs> = (data) => { console.log("login proceed", data); };
현재 click을 할 경우 login proceed가 console.log로 나타난다.
4. formState
form의 상태를 담고 있는 객체이다. 일반적으로 formState가 가지고 있는 error값을 핸들링할 때 자주 사용된다.
error={Boolean(errors.password)}
helperText={errors.password && "This field is required"}
현재 프로젝트에서는 다음과 같이 태그 내부에 error를 활용하여 비어있을 경우 에러를 보여주도록 만들어져 있다.
마무리
간단한 React-Hook-Form 라이브러리 사용법에 대해 배워보았다. 하지만 해당 폼은 직접적으로 api와 통신하지 않는 상태로, 클릭 시에 console.log를 출력하는 것이 전부인 상태다. 물론 이번 프로젝트에서 백엔드를 개발하지는 않지만, 가장 처음 세팅했던 라이브러리들 중 msw mock와 react-query를 활용하여 로그인이 실행되는 것과 같이 동작할 수 있게 해볼 예정이다.
'개발 > Next.js' 카테고리의 다른 글
| Next.js 시작하기 - 간단한 Login 페이지 만들기 (4) React-Query (1) | 2024.07.15 |
|---|---|
| Next.js 시작하기 - 간단한 Login 페이지 만들기 (3) React-Query (1) | 2024.07.06 |
| Next.js 시작하기 - 간단한 Login 페이지 만들기 (1) Router (5) | 2024.05.30 |
| Next.js 시작하기 - 프로젝트 세팅 (3) Zustand/Typescript+Jest/MUI (2) | 2024.05.28 |
| Next.js 시작하기 - 프로젝트 세팅 (2) (1) | 2024.05.19 |