작업 목표
- 리액트 훅 폼을 사용하여 에러 메시지 띄우기
- 원래 이슈 내용은 에러 메시지를 모달창으로 띄워달라는 것이었지만 입력해야 하는 폼 아래에 에러 메시지를 띄우는 게 더 낫다고 판단함. 이후 내부 회의를 거쳐 제안한 방향으로 결정됨.
- 모달창으로 하면 안되는 이유는 다음과 같음.
- 에러 메시지를 다시 확인하고 싶으면 또 저장을 눌러서 팝업창을 봐야 하는 번거로움이 있음.
- 에러 메시지를 보여주지 않으면 정확한 에러 위치를 찾을 수 없음. (모달창은 설명 뿐)
- errors가 객체 형태로 담기는데 api depth만큼 depth가 존재함.

a { b: {} c: {} d: [{...}] }
이런 형태면 모달창으로 모든 에러를 보여줄 때 재귀를 돌려서 message를 하나의 배열로 만들어야 함. 재귀가 어렵다기 보단 예쁘게 depth로 정리해놨는데 그걸 다시 푸는 게 이상하다고 생각함.
일단 훑어보시는 거라면 현황 요약 부분만 읽어도 좋아요~
react hook form 주의할 점
- errors
- 리액트훅폼은 register한 input의 옵션으로 넣은 규칙에 어긋나면 errors라는 객체에 register한 이름으로 에러를 담아줌.
- 🚨 errors는 같은 주소값을 사용하여 에러를 계속 갱신시켜 줌.

- setValue, getValue
- register한 input에 value를 set, get할 수 있음.
- input에 값을 입력하는 경우 외에도 값을 set해야 하는 경우에 유용하게 사용함.
- 🚨 상태가 업데이트 된다면 렌더링이 됨.

- setError, clearErrors
- setValue처럼 에러 발생을 직접 핸들링 해야 하는 경우에 유용하게 사용함.
- 🚨 input에서 관리하는 errors와 별개로 clearErrors를 사용해서 error를 지워줘야 함. 지워질 때까지 지속됨.

⭐ 코드가 어려워질 수 있는 포인트
1. input이 아닌 폼이 있을 때.

- 리액트 훅 폼은 Input에 register를 해야 input value 및 에러가 등록, 갱신이 되는데 input이 아닌 다른 것에 대해서도 등록이 필요한 상황이었음.
- 이런 경우 type이 hidden인 input을 만들어서 보이지 않는 input에 등록을 하고 setValue를 해준 곳으로 부터 value를 판별해 해당 value 여부를 확인함.
- 특히 유통사는 여러 곳에서 value를 넣어주고 있어 분명한 name을 가지고 있지 않음. 그래서 name이 일치하지 않아 error를 수동으로 통제해줘야 하는 경우가 됨.
onMatching={(_, a) => { setValue("a", a); clearErrors("a.code"); }} onRemove={() => { setValue("a", undefined); setError("a.code", _, { shouldFocus: true }); }} onUpdateName={async (_, name: string) => { setValue(`b`, [name]); forceRender(); }}
- 이렇게 수동으로 통제해주게 되면 앞으로 이러한 폼이 늘어날 때마다 setValue, setError, clearErrors를 해야 하는 문제가 발생함.
- 심지어 프론트 단에서 등록해서 보여줘야 하는 썸네일의 경우 기존의 썸네일이란 value 자체가 없었기 때문에 새로운 value를 추가해줘야 해서 기본으로 적어줘야 하는 코드가 더 필요함.
- 결국 문제를 요약하자면, register만 해주면 되는 게 아니라 value를 set하고 이 값에 따라 setError, clearErrors를
매칭되었을 때
,지웠을 때
,입력되지 않았을 때
등에 따라 일일히 적어줘야 함. 모든 경우를 통제하지 않고 unControlled하게 만들고 싶어 리액트 훅 폼을 쓰는 건데 이렇게 사용하게 되면 케이스를 빼먹기 쉽고 코드량도 복잡하게 증가하는 문제가 있음.
2. React.memo를 사용했을 때.
- 당시 프로젝트 현황은 다음과 같음.
- 폼이 많아 영상 관리와 달리 세부 컴포넌트로 대부분 분리된 상태 → errors를 props로 내려줘야 함.
- track이 100개가 넘는 경우가 있어 렌더링이 느려지는 이슈를 막기 위해 대부분의 컴포넌트가 HOC인 React.memo로 감싸져 있음.
- 그래서 생긴 문제점
- errors를 props로 내려주는데 주소 값이 같이 memo가 변경을 인식하지 못해 에러 메시지가 그려지지 않음. 또는 해결한 에러임에도 계속 에러를 띄우고 있음. (props가 error를 보여주는 컴포넌트까지 내려오지 않음)
- 이 문제를 해결하기 위해 억지로 errors의 주소 값을 변경시키는 코드가 들어감.
- 하지만 이러한 임시 방편에도 불구하고 여러 값이 존재하는 만큼 index에 따라 register를 했기 때문에 errors도 depth를 가짐.
- diff로 cloneDeep까지 해서 formErrors를 가공해주었지만 tracks 처럼 depth가 깊은 부분에서만 변경이 일어나면 이를 감지하지 못함.
- 이런 문제 때문에 값 컴포넌트만 formErrors의 값을 내려주어 depth를 한 단계 없앤 상태에서 다시 clone을 해서 내려줌 🤯 결국 depth를 거칠 때마다 에러의 주소 값을 변경해서 props로 내려주는 말도 안되는 코드가 탄생함..ㅜㅜ
errors를 context api로 관리하면 되지 않을까?
→ 좋은 방법이지만 errors를 set하고 clear하는 것도 같이 포괄하는 hook을 만들면 좋겠다고 생각해 next step이라 판단함. errors 뿐만 아니라 상태로 관리해야 하는 것들이 있는 상태라 산발적으로 어떤 건 context api로 관리하고 어떤 건 props로 내려주는 게 오히려 혼동을 일으킬 수도 있기 때문.
→ 추가로 상태로 관리했을 때 과연 렌더링 이슈가 발생하지 않을까에 대한 의문이 있음.
- 차라리 여기서 끝나면 다행이지만 어쨌든 이론적으로 주소값을 계속 바꾸면서 props를 내려주니 인식을 해야 하는데 tracks에 hidden input의 경우 errors가 깔끔하게 동작하지 않았음. 포커싱도 안되고 아직 이에 대한 정확한 원인은 파악하지 못함.
3. memo 등으로 errors의 싱크가 맞지 않을 때.
- 폼 상단에서 매칭했을 때 다른 폼에 자동으로 매칭되는 경우 (자동으로 값 삽입) 값이 채워졌음에도 불구하고 에러가 해결되지 않는 문제가 생김 (채워진 값을 지웠을 때도 다시 에러를 내야 하는데 그렇지 않음). 🤯
- 우선 파악한 내용으론 하위 컴포넌트까지 내려오는 errors가 상단 컴포넌트에 있는 errors와 싱크가 잘 맞지 않음. 메모 때문인지 register 문제인지.. 😭
- 에러가 하나 나면 원인이 날만한 곳이 여러 곳이라 파악하는 것조차 비용이 드는 상태임.
- 결국 상단 컴포넌트에서 handleSave와 handleError에서 setError, clearErrors를 사용해 억지로 validation을 한 번 더 함.
- 이 역시 register에서 동작해야 하는 부분이 제대로 동작하지 않아 일부 기능에 대한 코드를 넣은 거라 생각함. 최종적으론 없어져야 하지만 현재 동작을 안하니 억지로 validation 추가한 상태.
- input 값이 입력됐을 때 바로바로 인식을 못함. 그래서 해당 input에 onInput 이벤트를 걸어 값을 판단함.
- 이 역시 register를 했기 때문에 errors를 props로 받아 보여주기만 하면 될텐데 제대로 인식하지 않아 추가한 코드임.
현황 요약
- 리액트 훅 폼을 사용함으로써 적지 않아도 될 && 적지 말아야 할 코드가 엄청 많음.
- 산발적인 setError, clearErrors (추후 수정할 때 어디서 에러를 관리하고 있는지 모를 확률 높음)
- submit 시 errors에서 걸려야 할 것들이 걸리지 않아 추가로 해준 validation 코드’들’
- memo로 인한 errors 주소 값 변경 코드 존재. 그냥 억지로 새로운 객체를 만듦.
- 전체적으로 하나의 에러를 해결하기 위해 코드를 계속 덧대는 식으로 에러 메시지를 띄우다보니 에러 메시지에 대한 코드가 굉장히 산발적으로 존재하며 히스토리를 알지 않는 이상 코드를 새로 추가하긴 힘들거라 생각함.
해결 방법?
구조를 통채로 바꿔야 하는 방식들이라 더 나은 방법은 없는지 고민 중이에요.
- input인 값과 그렇지 않은 값을 two way로 관리하기.
- memo 없애기 → forceRender나 렌더링을 유발하는 setValue를 지워야 함(필수로 해줘야 하는 것들이 있어 없앨 방법은 뚜렷히 생각나지 않음).
- 리액트 훅 폼과 context api 조합해서 사용하기.
- 폼 입력 절차를 나눠 한번에 받는 폼 줄이기 (요거 좋은 것 같은데 역시 통채로 뜯어야 함).
