react-hook-form은 우리 회사의 공인(?) 된 상태 관리용 라이브러리다. 수 많은 input과, form 태그로부터 해방시켜준 고마운... 고마운 괴물이다.
친구라고 하기엔, 잘 알고 쓰지 않으면 안쓰느니만 못한 것 같다는 느낌을 요새 계속 받다보니, 차라리 괴물이라고 칭하고 싶다고 해야하나..?
최근 새로 개발하면서 사용상 주의해야되는 점이 2가지 정도 확인되서 남기려고 한다.
우선 짧은 것부터 시작하자.
그냥 내장 타입인 Date를 쓰시는 분들도 많겠지만, 나는 개인적으로 moment라는 패키지를 애용하고 있다.
그렇다보니 타입을 작성할 때, 다음과 같은 경우가 있다.
type customDate = {
momentDate : moment.Moment()
}
여기서 내가 간과한 것은, 각 라이브러리는 각자 기본 내장타입을 이용해 가공하는 절차가 분명히 있다는 점에 있다.
일단 react-hook-form 공식 docs 자체에서 moment나 luxon과 같은 커스텀 타입의 객체를 value로 활용하지 말라고도 하고 있기도 하고.
일단 나부터 Date 객체를 이리저리 꼬아가며 포매팅을 다르게 가져갈 때가 많은데. 라이브러리들은 훨씬 더 넓은 범위의 그것들을 가지고 있을텐데. 왜 moment 타입이 먹히지 않는거지? 하고 1시간 정도 바보같이 앓았다.
이게 무슨 소리냐... 라고 하면. 실제 useFieldArray 로직이 어떻게 되어있는지 보아야 한다.
https://github.com/react-hook-form/react-hook-form/blob/master/src/useFieldArray.ts
위는 실제로 react-hook-form에서 useFieldArray를 호출했을 때 실행되는 코드 중 한 조각에 해당한다.
const [fields, setFields] = React.useState(control._getFieldArray(name));
useFieldArray는 원본 배열을 직접 수정하는 것이 아니라, 별도의 state를 위와 같이 두고 있으며 원본 배열로부터는 값만 끌어오는 것으로 보인다.
알다시피, React는 Collection (이를테면 map을 이용한 렌더링 같은) 에 렌더링을 위한 Unique Key를 가지게끔 하는 룰이 있다.
<ul>
{array.map((item,index)=>{
<li key={index}></li>
}
</ul>
위처럼 코드를 작성하면, 어떠한 불명의 컴포넌트에서도 인덱스로 키를 잡아버리면 아마... 내가 알기로는 키를 부여한 것이 의미없이 모두 업데이트되는 구조로 되어있었던 걸로 기억나는데, 틀린 지식이라면 바로 잡아주시길 바란다.
아무튼, 이 문제에 대해 유연히 대처하기 위해서인지 useFieldArray에서는 각 배열 요소에 대해 독립된 키를 부여하고 있다.
가령,
<FormProvider>
<Container>
<First/>
<Second/>
</Container>
</FormProvider>
Container에서 react-hook-form을 이용해 값을 컨트롤한다고 가정해보자.
{
array:[]
}
대충 기본 값은 위처럼 되어 있고.
위의 값을 각 컴포넌트에 공유시켜주기위해, 별도의 아래와 같은 훅을 작성한다고 쳐보자.
const array = useFieldArray({control,name:'array'})
return {array}
// useCustomShareValues.ts
여기서... First 컴포넌트에서 가져온 array와 Second 컴포넌트에서 가져온 array가 같겠거니.. 라고 처음 생각했었는데 아뿔싸. 자동으로 생성된 ID값이 다르네?
결론적으로 겉으로는 같은 객체를 가리키고 있는 것으로 보이나, 그렇지 않았던 것이다.
결국 이 문제를 어떻게 해결했냐면.
- First 컴포넌트는 보여주기만 하면 된다.
- Second 컴포넌트는 array를 update해야한다.
- update를 setValue로 돌리기엔 공수가 많이 든다. 그러니까 Second 컴포넌트에서는 정상적으로 useFieldArray의 update를 쓴다.
- First 컴포넌트에서 렌더링할 때는 useFieldArray를 쓰긴 쓰되, 단순히 key의 고유성과 배열의 인덱스로만 활용하고 실제 값은 getValues를 활용하여 그려준다.
useController를 너무 유용하게 쓰고 있다보니, 일상적으로 배열도 똑같은 값 가져와주겠지~ 라고 맹신했던 탓에 생긴 문제였고, 이마저도 자체 QA 중에 왠만하면 발생하지 않을 것 같은 케이스도 테스트해보다가 발견했다.
차라리 굳이 컴포넌트를 나누지 말 걸, 이라는 생각이 든다. 지금이라도 합칠까? 고민하고 있는 와중이다.
이런 걸 보면, 추상화와 재사용에 너무 열중하는 것 또한 개발 효용성에 있어서 나쁜 점으로 다가오는 것 같다.
Docs를 잘 읽지 않는 내 단점이 또 상기되기도 하고..
또... 생각보다 이상한 시나리오로 움직이는 사용자들도 많다는 걸 느끼고 있다보니
기획자가 좋은 기획과 디자인을 주고 있긴 하지만서도, 이 케이스처럼 계속 의심해나가자는 생각이 들었다.
누군가가
컴퓨터 죤내 사기꾼!!!
이라고 했던 게 기억나는데...
개발하면 개발할 수록 컴퓨터는 누구보다 정직하며, 내 두뇌가 사기꾼이 아닌가 싶다.