MUI Form 제어하기

Jemin·2023년 12월 1일
0

개발 지식

목록 보기
40/51
post-thumbnail

서론

React를 사용하면서 form의 input들을 제어하다보면 많은 상태가 생기고 그로 인해서 코드가 더러워진다.

한두개 정도만 사용한다면 많이 필요하지는 않지만, 4개 이상(이름, 아이디, 비밀번호, 비밀번호 확인) 넘어가면서 코드가 엄청 길어지고 원하는 UI를 구현하기 위해 useState가 많아지는 것을 느낄 수 있다.

그래서 자주 사용했던 라이브러리가 react-hook-form이었는데, MUI를 사용해서 UI를 구성하다보니 react-hook-form을 적용하는 방법이 달라지게 된다.

react-hook-form 라이브러리는 직접 DOM에서 읽어오는 방식이라 일반적으로 사용하는 input 태그라면 쉽게 사용할 수 있지만, MUI는 상태를 외부에서 관리하기 때문에 라이브러리에서 제공하는 register함수를 사용할 수 없다.

그렇다고 아예 사용하지 못하는 것은 아니다. react-hook-form라이브러리에서 제공하는 Controller컴포넌트나 useController 훅을 통해서 제어할 수 있다.

[React Hook Form] mui와 같이 사용하기 | Controller, useController

위 글에서 MUI와 react-hook-form 라이브러리를 같이 사용하는 방법을 설명하는데, 기존의 register함수를 사용하는 것 보다 코드가 길어지고 복잡해진다.

스택오버플로우나 여러 블로그에서 MUI의 form 제어를 위한 라이브러리로 formik 라이브러리를 추천한다. 그래서 이번에 사용해본 것을 정리하려 한다.

formik

formik의 공식문서에서는 MUI에 적용하는 방법을 간단하지만 자세하게 알려준다.

[FORMIK docs] Material UI

MUI를 사용하면서 TextField 컴포넌트에 필요한 옵션들 대부분을 예시로 보여주기 때문에 공식문서만 보고도 금방 적용할 수 있다.

아래는 MUI와 formik 라이브러리를 사용한 간단한 로그인 form 코드다.

// import...
// 로그인 컴포넌트
export const SignIn = () => {
    const { mutate, isPending } = usePostSignIn(); // post요청을 위한 tanstack query 훅
    const formik = useFormik({
        initialValues: { // 각 input의 초기값
            userId: "",
            password: "",
        },
        validationSchema: signInSchema, // 유효성 검사
        onSubmit: (form) => { // submit 함수 (input값들을 객체로 받는다)
            mutate({
                userId: form.userId,
                password: form.password,
            });
        },
    });

    return (
        <Card>
            <form onSubmit={formik.handleSubmit}> // submit 함수 바인딩
                <CardContent
                    sx={{
                        display: "flex",
                        flexDirection: "column",
                        gap: 3,
                        width: 300,
                    }}
                >
                    <CardHeader title="Sign In" />
                    <Input label="ID" name="userId" formik={formik} />
                    <Input
                        label="Password"
                        name="password"
                        formik={formik}
                        type="password"
                    />
                    <CardActions>
                        <LoadingButton
                            loading={isPending}
                            variant="contained"
                            type="submit"
                            endIcon={<Send />}
                        >
                            submit
                        </LoadingButton>
                    </CardActions>
                </CardContent>
            </form>
        </Card>
    );
};

// Input 컴포넌트
export const Input = ({ label, name, formik, type = "text" }) => {
    return (
        <TextField
            label={label}
            name={name} {/* name이 있어야 formik에서 input을 식별할 수 있다. */}
            value={formik.values[name]} {/* formik에 저장된 입력값 */}
            onChange={formik.handleChange} {/* 값이 변할 때마다 formik에 넘겨준다 */}
            onBlur={formik.handleBlur} {/* blur 상태가 변할 때마다 formik에 넘겨준다 */}
            helperText={formik.touched[name] && formik.errors[name]} {/* 사용자가 input을 사용중인지 */}
            error={formik.touched[name] && !!formik.errors[name]} {/* 해당 input이 유효성 검사에 실패했는지 */}
            size="small"
            autoComplete="off"
            type={type}
        />
    );
};

useFormik 훅을 사용해서 formik값을 받고 TextFieldformik의 값을 바인딩, 훅에 옵션을 넣어 form을 쉽게 제어할 수 있다.

formik을 한번에 받는게 아닌, 객체 분해 할당을 사용해 각각의 값들을 받아올 수도 있다.

const {handleSubmit, ... } = useFormik({옵션})

여기서는 Input 컴포넌트를 공용으로 만들어서 사용하기 때문에 파라미터가 많아지는게 싫어서 하나로 받았다.

formik의 많은 값들을 input에 바인딩 해주는 이유

이렇게 바인딩하는 방식의 코드를 처음보면 굳이 저렇게 많은 값들을 input에 넣어주어야 하나 싶은데, 주석에도 작성했듯이 input에 대한 여러 정보들을 formik이 알기 위해서 필요한 정보들마다 값을 바인딩해주는 것이다.

저렇게 해주지 않으면 필요한 값이 있을 때마다 직접 useState를 사용해서 하나하나 가져와야 한다. 위 코드의 동작과 동일하게 만드려면 input태그 하나당 useState가 최소 4개씩은 필요할 것이다.

우리가 input 값을 관리하고 싶으면 onChangevalueuseState의 값과 set함수를 넣는 것처럼 formik도 필요하기 때문에 바인딩하는 것이다. 값만 바인딩해주면 관리 자체는 useFormik에서 한번에 처리할 수 있기 때문에 확실히 코드가 줄어들고 복잡성 또한 낮아진다.

Yup

스택오버플로우나 다른 블로그에서 formik라이브러리를 사용하면 꼭 같이 사용해주는 것이 있는데, Yup이 바로 그 라이브러리다.
각종 유효성 검사를 위한 라이브러리인데 formik은 유효성 검사 객체를 옵션으로 받기 때문에 주로 같이 사용된다.

위에서 먼저 본 코드에 formik.errors라는 코드가 있는데 여기서 가져오는 에러가 Yup을 통해 유효성 검사에 실패했을 경우 가져오는 메시지다.

import * as yup from "yup";

const errorMessages = {
    required: "필수 입력 항목입니다.",
    regexEngNum: "영문, 숫자만 입력 가능합니다.",
    min: (min) => `최소 ${min}자 이상이어야 합니다.`,
    max: (max) => `최대 ${max}자 까지만 가능합니다.`,
    confirmPassword: "비밀번호가 일치하지 않습니다.",
    regexNum: "숫자만 입력 가능합니다.",
};

const regexEngNum = /^[A-z0-9]+$/;

export const signUpSchema = yup.object({
    name: yup
        .string()
        .required(errorMessages.required)
        .matches(regexEngNum, errorMessages.regexEngNum)
        .min(4, errorMessages.min(4))
        .max(10, errorMessages.max(10)),
    userId: yup
        .string()
        .required(errorMessages.required)
        .matches(regexEngNum, errorMessages.regexEngNum)
        .min(4, errorMessages.min(4))
        .max(20, errorMessages.max(20)),
    password: yup
        .string()
        .required(errorMessages.required)
        .matches(regexEngNum, errorMessages.regexEngNum)
        .min(4, errorMessages.min(4))
        .max(20, errorMessages.max(20)),
    confirmPassword: yup
        .string()
        .required(errorMessages.required)
        .matches(regexEngNum, errorMessages.regexEngNum)
        .min(4, errorMessages.min(4))
        .max(20, errorMessages.max(20))
        .oneOf([yup.ref("password"), null], errorMessages.confirmPassword),
});

로그인은 예시가 많이 간단해서 회원가입 부분을 가져왔다. yup 라이브러리를 사용하면 정규표현식 외에도 여러 유효성 검사가 가능하고 스키마 객체로 만들어서 반환해주기 때문에 쉽게 사용할 수 있다.

마무리

"라이브러리를 이렇게 사용해야 좋다", "내가 예시를 보여주겠다" 보다는 "MUI를 사용할 때 form 제어를 한다면 이런 라이브러리도 있더라" 정도의 기록이다.

난잡한 이런 글보다 공식문서를 보는 것이 훨씬 좋다. 그냥 이런 라이브러리도 있구나 하고 공식문서로 들어가서 읽고, 배우고, 사용해보는 것이 좋다.

각 라이브러리에 대한 공식문서
Formik
Yup

참고
Yup 라이브러리 파헤치기
[FORMIK docs] Material UI

profile
경험은 일어난 무엇이 아니라, 그 일어난 일로 무엇을 하느냐이다.

0개의 댓글