[React] 복잡한 useEffect...

Heera1·2024년 6월 3일

React 사용

Toast ui Grid에서 Row를 더블 클릭했을 때 해당 행의 데이터를 상태관리툴에 저장하고 => 그 밑에 등록영역 ui에 해당 값들을 input의 value로 뿌려주는 과정에서 만난 어려움을 글로 남긴다.

const initialCheckboxState = {};
    dummyData.forEach(item => {
        initialCheckboxState[item.name] = false;
    });
const [supplierCheckBoxState, setSupplierCheckBoxState] = useState([]); //선택한 값, 배열로 담아두는 곳
const [supplierCheckState, setSupplierCheckState] = useState(initialCheckboxState); //리스트 생성, true false값 저장
  • 메뉴 DB가 아직이라 일단은 dummyData를 만들어 사용했지만, DB에서 뽑아 쓰고, 리스트가 실시간으로 추가될 수 있기 떄문에 useState에 하드코딩을 할 수가 없어(양도 많기도 하고)
    위와 같이 작성하여 리스트들의 checked 유무를 관리하는 객체를 먼저 만들었다.



문제는 Grid에서 Row를 뽑아와서 등록영역에 뿌려줄 때이다.

//Zustand Store
export default function MarkupGrid({ gridData }) {
    const gridRef = useRef(null);
    const { insertMarkUpInputs, deleteGridRow, setInsertMarkUpInputs, setDeleteGridRow } = useMarkUp();
    const { insertDefartureAndArrivalState, setInsertDefartureAndArrivalState } = useDefAndArrival();


    //그리드의 행 더블클릭 시 해당 행의 값 저장
    const onClickGridSelect = (e) => {
        const { rowKey } = e;
        const row = gridData.find((item) => item.rowKey === rowKey);
        if (!row) {
            return;
        }
        const selectGridRowData = {
            iMarkUpID: row.markUpID || '',
            iMarkUpTitle: row.markUpTitle || '',
            iSupplierID: row.supplierID || '',
            //생략
        }

        const selectGridRowData2 = { //다른 곳에서도 사용하기 때문에 따로 store를 만들어줬다.
            iDepartureType: row.departureType || '',
            iDepartureCode: row.departureCode || '',
            iArrivalType: row.arrivalType || '',
            iArrivalCode: row.arrivalCode || '',
        }
        setInsertMarkUpInputs(selectGridRowData);
        setInsertDefartureAndArrivalState(selectGridRowData2);
    }

    //check한 행 저장
    const checkGridRow = (e) => {
        const { rowKey } = e;
        const row = gridData.find((item) => item.rowKey === rowKey);
        if (!row) {
            return;
        }

        setDeleteGridRow([...deleteGridRow, row.markUpID]);
    }
    console.log("더블클릭시 저장1 ===>", insertMarkUpInputs)
    console.log("더블클릭시 저장2 ===>", insertDefartureAndArrivalState)
    console.log(deleteGridRow);

    /*임시 dummyData*/
    const gridOptions = {
        columns: [
            { name: 'markUpID', header: '마크업IDX' },
            { name: 'markUpTitle', header: '제목' },
            { name: 'sellerID', header: '판매사' },
           //생략
        ],
        header: {
            height: 50,
            complexColumns: [
                {
                    header: '마크업',
                    name: '11111111111111',
                    hideChildHeaders: false,
                    childNames: ['markUpType', 'ACI']
                },
               //생략
            ],
        },
        rowHeaders: ['checkbox'],
        bodyHeight: 400,
    }

    useEffect(() => {
        TuiGrid.applyTheme('clean');
    }, []);

    return (
        <Grid
            ref={gridRef} // 그리드 참조 설정
            data={gridData}
            columns={gridOptions.columns} {...gridOptions}
            onDblclick={onClickGridSelect}
            onCheck={checkGridRow}
            onUncheck={(e) => console.log("onUncheck")}
        />
    )
}

zustand를 설정하고, 그리드를 사용하는 페이지에서 해당 그리드 컴포넌트와 store를 import한다.

//page

const [insertGroupData, setInsertGroupData] = useState({
    iMarkUpID: '', iMarkUpTitle: '', iSupplierID: '', iSellerID: '', //마크업ID //제목 //공급사ID /판매사ID
    //생략
})

const { insertMarkUpInputs, deleteGridRow, removeAllInput } = useMarkUp();
const { insertDefartureAndArrivalState, removeDefAndArrivalInput } = useDefAndArrival();

 useEffect(() => {
     console.log("디버깅1", checkboxState);
     console.log("디버깅2", insertMarkUpInputs);
     console.log("디버깅3", insertDefartureAndArrivalState);

     setInsertGroupData(prevData => ({
         ...prevData,
         ...insertMarkUpInputs,
         ...insertDefartureAndArrivalState,
         iMarkUpID: insertMarkUpInputs.iMarkUpID || '',
         iMarkUpTitle: insertMarkUpInputs.iMarkUpTitle || '',
         iSupplierID: insertMarkUpInputs.iSupplierID || '',
         iSellerID: insertMarkUpInputs.iSellerID || '',
         //생략

         iDepartureType: insertDefartureAndArrivalState.iDepartureType || '',
         iDepartureCode: insertDefartureAndArrivalState.iDepartureCode || '',
         iArrivalType: insertDefartureAndArrivalState.iArrivalType || '',
         iArrivalCode: insertDefartureAndArrivalState.iArrivalCode || '',

         //생략
     }));


     //판매사
     const sellers = insertMarkUpInputs.iSellerID.split(',').map(seller => seller.trim());
     setSellerCheckBoxState(sellers);


     //여정타입
     //좌석등급과 동일한 상황이어서 if문 추가
     const tripTyps = insertMarkUpInputs.iTripType ? insertMarkUpInputs.iTripType.split(',').map(trip => trip.trim()) : [];

     if (insertMarkUpInputs.iTripType.length !== 0) {
         setCheckboxState({
             oneWay: tripTyps.includes("RW"),
             roundTrip: tripTyps.includes("OW"),
         })
     } else {
         setCheckboxState({
             oneWay: true,
             roundTrip: true
         });
     }

     //좌석등급
     //처음 랜더링 될 때 useEffect로 랜더링 되면서 iCabin의 초기값이 ""가 되어
     //처음 랜더링될 때 checkboxState가 true, ture 여야하는데 두번째 랜더링 때 false가 되어 버려 디폴트가 선택된 상태가 도무지 되지 않아. if문을 추가해 줌
     const cabins = insertMarkUpInputs.iCabin.split(',').map(cabin => cabin.trim());
     if (insertMarkUpInputs.iCabin.length !== 0) {
         setCabinCheckState({
             Y: cabins.includes('Y'),
             P: cabins.includes('P'),
             C: cabins.includes('C'),
             F: cabins.includes('F')
         })
     } else {
         setCabinCheckState({
             Y: true,
             P: true,
             C: true,
             F: true
         })
     }
 }, [insertMarkUpInputs, insertDefartureAndArrivalState]);
  • insertGroupData : 등록영역 전체 input들의 상태를 관리하는 useState이다. post 요청 시 해당 useState의 값들을 보낸다.
  • 그리드를 더블클릭 했을 때 store에 저장했던 값들을 불러온다. useEffect를 사용하여, 해당 값들이 바뀐다면 useEffect가 실행되도록 한다.


에러 발생

  • iDepartureCodeiArrivalCode 가 textarea에 값이 들어가졌다가 랜더링이 다시 되면서 값이 초기화되는 문제가 발생했다.
  • 다른 페이지에서도 똑같은 방법으로 사용하고 있었지만 정상적으로 작동하고 있었기 때문에 해당 페이지에 있는 다른 useEffect 가 실행되면서 또 다시 랜더링되는 게 아닌가 의심하게 되었고, 의심이 되는 useEffect를 주석처리하고 실행했을 때 정상적으로 작동하는 것을 확인했다. 문제가 되던 useEffect는 아래와 같다.
useEffect(() => {
        const updatedCheckboxState = {};
        dummyData.forEach(item => {
            updatedCheckboxState[item.name] = supplierCheckBoxState.includes(item.name);
        });
        setGroupData(prevData => ({
            ...prevData,
            ...insertInputs,
            iSupplierID: insertInputs.iSupplierID || '',
        }));

        setSupplierCheckState(updatedCheckboxState);
    }, [supplierCheckBoxState]);
  • 해당 useEffect는 그리드에서 더블 클릭 했을 때 저장한 데이터중 "메뉴"의 데이터에 따라 등록영역의 리스트에 "메뉴"에서 알맞은 항목에 checked를 하는 역할을 한다.
    -setGroupData() 부분에서 데이터의 변화가 생기면서 useEffact가 한 번 더 실행되어 랜더링이 한 번 더 일어나는 문제였다. 해당 부분을 삭제하면서 정상적으로 작동하는 것을 확인했다.
  • 사실상 위의 useEffect에서 iSupplierID에 이미 값을 할당하고 있기 때문에 해당 useEffect의 setGroupData 부분을 쓸모가 없는 중복 코드였다.
profile
웹 개발자

0개의 댓글