JustDoEat
[react/문제해결] 리스트를 useRef훅을 이용해 상태관리를 해보자(+input value에 객체저장/재구조화) 본문
[react/문제해결] 리스트를 useRef훅을 이용해 상태관리를 해보자(+input value에 객체저장/재구조화)
kingmusung 2024. 7. 9. 00:36개요.
https://github.com/2024-EveryCare/frontend
약 이름으로 약을 검색하였을 때. 위 사진처럼 약 정보가 쭉 나오게 된다.
1. 사용자가 선택을 하면 해당요소에 있는 약들의 Value값을 저장을 해야 하고.
2. 사용자가 선택을 할 시 시각적으로 선택이 되었는지 안되었는지 보여줘야 한다.
<ul>
<li>
<PillNextText headText={medicine.drugName} /> // 작은 알약 그림 옆에 텍스트를 표시해주는 컴포넌트
</li>
</ul>
서버에서 불려 와 진 알약들의 이름을 pillNextText에 넣은 후 표시를 해주어야 한다.
일단 표시해 주는 건 매우 간단하다. 하지만 내가 생각한 문제점은
- 사용자가 선택을 했을 때 여러 개의 리스트가 있는데 이중에 어떤 Value값을 선택 후 저장을 해야 하는지. 눌렀을 때 배경색을 바꿀 수 있는지.(요소가 하나만 있다면 어렵게 생각도 안 했지만, 동적으로 여러 개의 리스트 중 하나만 뽑는 게 어떻게 가능할지.)
- 동적으로 생기는 많은 리스트 중에 어떤 요소를 가지고 와야 하는지, 이게 가능한 건지
useRef를 쓰게 된 개기
"check 기능이니까. input태그에 checkbox 속성을 가져다 쓰고 싶다.. 하지만.. 스타일 자체가 투박하고 퍼블리싱과 너무 다른데..
그냥 div태그에 input태그에 checkbox속성만 가지고 와서 쓰고 싶다..."
라는 생각에 찾아본 결과..
useRef란
useRef는 React Hook 중 하나로, 주로 DOM 요소에 접근하거나(참조를 생성해서), 컴포넌트가 다시 렌더링될 때에도 값이 변하지 않는 변수를 관리하는 데 사용됩니다.
주요 특징
- DOM 요소 접근: useRef를 사용하여 DOM 요소에 직접 접근할 수 있습니다. 이는 클래스형 컴포넌트의 createRef와 비슷한 역할을 합니다.
- 값의 유지: useRef는 컴포넌트가 다시 렌더링되더라도 값을 유지합니다. 이는 컴포넌트의 상태를 저장할 필요는 없지만, 값을 기억해야 할 때 유용합니다.
- 값 변경시 리렌더링 안 됨: useRef의 값이 변경되어도 컴포넌트는 리렌더링 되지 않습니다. 이는 useState와의 차이점이다!!(중요)
이 특징을 기억하고 아래 사용법을 보면 이해가 잘 될 것이다.
사용.
1. 기존 코드에 input태그를 집어넣고 스타일을 None, hidden으로 숨겨준다. 그냥 내버려 두면 누가 봐도 이상하겠죠?
=> 숨기면 어떻게 check를 해요..
<ul>
<li>
<PillNextText headText={medicine.drugName} /> // 작은 알약 그림 옆에 텍스트를 표시해주는 컴포넌트
</input className="hidden"> // input태그를 추가 but 안보이게 숨겨줄거임.
</li>
</ul>
2. 선언을 하고 "ref={}"를 이용해 중괄호 안에 useRef를 넣어준다.
=> 이렇게 되면 useRef를 이용해서 간접적으로 조작을 할 수 있다.
(함수에서 checkboxRefs.current.value = 0 이런 식으로 조작이 가능. )
const checkboxRefs = useRef<(HTMLInputElement | null)[]>([]);
const checkedBgRefs = useRef<(HTMLLIElement | null)[]>([]);
=> useRef를 선언해 준다, 타입은 여러 개의 요소가 와야 해서 배열로 지정
(HTMLInput, HTMLLI 요소가 들어갈 수 있고, 아무것도 안 들어갈 수도 있기 때문에 타입을 위와 같이 지정)
</input className="hidden" ref={checkboxRefs}
//내가 ref를 써준다.
자바스크립트의 HTML문서의 요소를 선택하기 위해 아래와 같은 선택자를 써서 요소를 조작했었다. 이 개념으로 이해하면 편하다
- getElementsByClassName
- getElementsByName
- querySelectorAll
3. 하지만 여러 개의 요소를 다루어야 하므로 그냥 ref를 사용하면 못 알아듣는다.
{searchedDrugData &&
searchedDrugData.map((medicine, index) => (
<li
key={index}
onClick={() => handleCheckboxChange(index)}
ref={(element) => (checkedBgRefs.current[index] = element)}
>
<PillNextText headText={medicine.drugName} />
<input
type="checkbox"
className="hidden"
value={JSON.stringify(medicine)} // 서버로 보내줄때 약 이름을 주기로 해서 이름을 저장했음.
ref={(element) => (checkboxRefs.current[index] = element)}
/>
</li>
))}
2번까지는 간단한 예시였다면 이건 진짜 코드를 가지고 왔다.
ref={(element) => (checkboxRefs.current[index] = element)}
ref={checkboxRefs}에서 자세히 보면 코드가 바뀌었다.
소괄호 안에 element는 React 컴포넌트 내에서 해당 DOM 요소에 대한 참조를 나타내는 매개변수이고, 이 매개변수는 일반적으로 DOM 요소 자체를 나타냄.
2번까지는 하나의 태그를 하나의 Ref에 넣어주므로 그냥 ref={checkboxRefs} 이런 식으로 넣었던 것이고.
하나의 ref에 여러 태그를 넣고 싶다면 위처럼 구체적으로 지정을 해주어야 한다.
(map 함수를 통해 요소가 하나 빠질 때마다. 리스트들이 생성이 되므로 / map함수를 돌릴 때 index번호까지 받으므로써 완벽해짐.)
4. 배경색을 주기 위해 li태그도 똑같이 ref를 사용해서 지정.
5.ref에 요소를 넣었으니 꺼내서 값을 변경해 보자
li 태그를 클릭하면 check박스의 값이 바뀌어야 한다. 그러므로 이벤트리스너를 걸어주고, 체 그 박스의 값을 바꾸는 함수를 보자.
(li태그에 걸린 onClick에 있는 함수임.)
const handleCheckboxChange = (index: number) => {
// 토글 기능만 하는 함수
if (checkboxRefs.current[index]) {
console.log(checkboxRefs.current[index]?.value);
if (checkboxRefs.current[index]!.checked == true) {
//체크박스가 선택이 안되어 있다면 -> 선택을 한 후 -> 배경색, 선택되어진 약 저장하는 함수로 이동
checkboxRefs.current[index]!.checked =
!checkboxRefs.current[index]!.checked; // checked 토글 역할
handleCheckboxBgChange(index);
} else if (checkboxRefs.current[index]!.checked == false) {
console.log('선택됌.');
checkboxRefs.current[index]!.checked =
!checkboxRefs.current[index]!.checked; // checked 토글 역할
handleCheckboxBgChange2(index);
}
}
};
그 외.
check박스의 값이 변하면 배경색을 변경함과 동시에 useState에 값을 넣거나 빼는 함수이다. 위에서 설명한 함수와 연결이 되어있다.
const handleCheckboxBgChange = (index: number) => {
// 선택 해제 모드
console.log(checkedBgRefs.current[index]);
checkedBgRefs.current[index]?.classList.remove('bg-blue-100');
const drug = checkboxRefs.current[index]!.value; // 삭제 할 약의 값
const { drugCode, ...restDrugData } = JSON.parse(drug); // 문자열로 바꾼후, 재구조화를 통해서 drugCode만 추출.
console.log('drugCode:', drugCode);
const temp = savedDrug.filter(
(drugData: DrugData) => drugData.drugCode != drugCode,
); // 지우려는 데이터를 뺀 나머지 항목을 다시 저장
setSavedDrug(temp);
};
const handleCheckboxBgChange2 = (index: number) => {
// 선택 모드
console.log(checkedBgRefs.current[index]);
checkedBgRefs.current[index]?.classList.add('bg-blue-100');
const temp = checkboxRefs.current[index]?.value; //json형식으로 파싱되어 있는 value값을 받아옴
const drug = JSON.parse(temp); // input값의 객체를 원래 DrugData객체로 다시 파싱해줌.
// setSavedDrug(...savedDrug, drug);
setSavedDrug((preSavedDrug: DrugData) => [...preSavedDrug, drug]);
};
checkedBgRefs.current[index]?.classList.remove('bg-blue-100');
checkedBgRefs.current[index]?.classList.add('bg-blue-100');
위에서 설명한 querySelector랑 비슷하다.
ref에 저장된 요소들의 배경색을 바꾸는 예시를 마무리로 마치도록 하겠습니다!!!
+ 재구조화(input태그의 value에 객체를 저장하는 법.)
input 태그의 value에 객체는 원래 올 수 없다. 하지만 약의 이름만 추출해서 랜더링을 하지만, 서버로 다시 보내기 위해는 약의 코드 또한 요하므로. 결국 저장은 input태그의 value에 해야 한다.
1. 객체를 JSON형식의 문자열로 파싱 하자. (랜더링에는 medicine.drugName 만 필요하지만, medicine이라는 객체를 저장해야 함)
<input value={JSON.stringify(medicine)}>
=> JSON객체가 아니라 객체나, 배열을 JSON형식의 "문자열"로 바꾸는 거임
2. 재구조화를 통한 데이터 추출
const drug = checkboxRefs.current[index]!.value;
// 삭제 할 약의 데이터(제이슨으로 파싱한 문자열)
const { drugCode, ...restDrugData } = JSON.parse(drug);
// 문자열로 바꾼후, 재구조화를 통해서 drugCode만 추출.
문자열로 바뀐 데이터를 JSON객체로 다시 파싱한후, 재구조화를 한다면, drugCode 에는 drug.drugCode만 담길 것이고, restDrugData에는 나머지 데이터들이 담긴다.