[react-hook-form] MUI에서 react-hook-form & Redux-ToolKit을 이용해 데이터 관리하기

루나·2022년 8월 24일
0

여러 외부 라이브러리를 사용하다보니 어느부분에 어떻게 사용해야 하는지 헷갈려서 정리하기로 했다.
예시로 들 프로그램은 MUI로 구성된 폼을 react-hook-form을(이하 RFH) 사용해서 유저가 입력한 데이터를 Redux(ToolKit)로 저장하는 간단하지만 여러 외부 라이브러리가 섞여 복잡해진 로직이다.

MUI & RTK

기본 뼈대가 되는 UI작업을 MUI을 사용해서 하기로 했다.

 <Dialog open={open} onClose={onClose}>
     ...
                  <TextField label="이름" value={name} />
                  <FormControlLabel
                    label="접근 제어"
                    control={<Checkbox checked={!!accessControl} onChange={handleCheck} />}
                  />

              <Button sx={{ width: 100 }} color="primary" variant="contained">
                {Strings.contents.TEXT_CONTENTS_REGISTRY_BUTTON}
              </Button>
	 ...
 </Dialog>

여기서 실제 데이터인 name과 accessControl은 Redux store를 사용해 관리하고 있다. (useSelector)
handleCheck로 accessControl을 관리한다. (useDispatch)

이제 이 폼에서 버튼을 누르면 TextField와 Checkbox에 있는 값을 불러와 원래 값에 dispatch를 해야하는데 이를 처리하는 것을 RHF을 사용해 하려한다.

MUI & RTK + RHF

RHF는 기본적으로 직접 제어되지 않는 요소에 사용되지만 직접 제어를 하는 외부 UI 라이브러리와 같이 사용하기 위해선 Controller를 사용하면 된다.
라고 써있지만 실제로 찾아보면 이걸 도대체 어떻게 써야하는지 감이 잘 안오고 어떻게 써보더라도 값이 제대로 넘겨지거나 수정되지 않는 현상을 볼 수 있다.

  const dispatch = useDispatch<AppDispatch>()
  const categoryDetail = useSelector((state: RootState) => state.categoryDetail.data)
  const categoriesViewList = useSelector((state: RootState) => state.categoryView.data)
  const [open, setOpen] = useState(false)
  const { control, handleSubmit, reset } = useForm<Tables>()
  
  useEffect(() => {
    if (categoryDetail.id !== UNSET) {
      setOpen(true)
    }
  }, [categoryDetail, categoriesViewList])

  //중요!!!
  useEffect(() => {
    reset(categoryDetail)
  }, [categoryDetail])

  const onClose = () => {
    setOpen(false)
    dispatch(setCategory(initialState.data))
  }

  const onSubmit: SubmitHandler<Tables> = data => {
    ...
    //Controller의 name=""부분
    const { name, accessControl } = data
    ...
  }

여기서 useEffect()에 있는 reset(data) 부분에 (RTK에서 받아온)실제 데이터를 넣어줘야 RHF에서도 해당 데이터를 사용할 수 있다.
RHF 내부에서 사용하는 상태에 Redux 값을 복사해주는 핵심 부분이며 Redux의 값이 변할때마다 다시 초기화해줘야 하니 의존성에도 잊지말고 넣어주자.
사실 이부분을 넣어주지 않아서 계속 리덕스 스토어에 있는 값과 RHF에 있는 값이 따로놀아서 n시간동안 문제였다...

  1. useForm 안에 따로 저장소가 있으며 useForm() 안에 인수로 기본값 처리 가능.
  2. reset(데이터)로 RHF의 저장소에 직접 저장 가능.
  3. render의 콜백에서 받아온 field.value 안에 해당 데이터가 존재하며 접근 가능.
  4. render 안에서 field.onChange를 사용하면 field.value의 값 변경 이벤트를 지정할 수 있으며 기본적으로 제공되는 이벤트가 존재(이건 MUI쪽에서 받아오는 거 같은데 정확하진 않다.)
<Dialog open={open} onClose={onClose}>
      ...
                  <Controller
                    name="name"
                    control={control}
                    defaultValue={name}
                    render={({ field }) => <TextField {...field} />}
                  />
                  <Controller
                    name="accessControl"
                    control={control}
                    render={() => (
                      <FormControlLabel
                        control={<Checkbox checked={!!accessControl} onChange={handleCheck} />}
                        label="접근 제어"
                      />
                    )}
                  />

              <Button
                type="submit"
                sx={{ width: 100 }}
                color="primary"
                variant="contained"
                onClick={handleSubmit(onSubmit)}
              >
               ...
</Dialog>
<Controller
  name="name"
  control={control}
  defaultValue={name}
  render={({ field }) => <TextField {...field} />}
  />

위와 같이 Controller에서 RHF의 onSubmit 함수에서 인수로 받아와 처리할 변수 이름을 name=에 집어넣고 control=에 useForm에서 받아온 control을 넣어준다.

render={({ field }) => <TextField {...field} />}

TextField의 경우 dispatch를 사용하지 않으니 체인지 이벤트에 관해서 기본적으로 제공해주는 onChange 이벤트(handleCheck)로 값을 수정하면 된다.

render={() => (
  <FormControlLabel
    control={<Checkbox checked={!!accessControl} onChange={handleCheck} />}
    label="접근 제어"
    />
)}

하지만 Checkbox의 경우 눌렀을때 값이 변하는 이벤트를 dispatch로 보내줘야 했기 때문에 onChange에 dispatch 이벤트를 넣어줘야 한다.


MUI에서도 기본적인 컨트롤을 제공해주고 RHF에서도 따로 컨트롤이 있었으며 Redux에 저장되어있는 값으로 기본값을 넘겨줘야 했기 때문에 여러 라이브러리의 공식문서를 다 참고해야 했다.
사실 크게 어려운부분도 아니었던 거 같은데 라이브러리가 어떻게 동작하는지, 어떻게 사용해야하는지 몰라서 시간을 굉장히 잡아먹었던 거 같다. 아직도 완전히 알진 못하는 거 같고 역시 외부라이브러리를 사용하는데엔 장점과 단점이 있는 거 같다.


나중에 다시 보니 정말 개판이다... 리덕스에서 받아온 값은 useForm의 초기에만 기본값으로 사용하기 위해 넘겨주면 되며 이후 변경사항은 submit을 성공적으로 수행하면 서버에 데이터가 실제로 변했을 것이기 때문에 다시 서버에서 업데이트를 받으면 됐을 것 같다.

profile
백엔드 개발자

0개의 댓글