본문 바로가기

React

React 체크박스를 통한 배열 렌더링 및 Cleanup(with material-ui, notistack)

반응형

test 환경을 위해 create react app 을 통한 리액트 프로젝트를 생성

npx create-react-app react-array-cleanup

이 후, 테스트에서 사용될 ui 패키지(@material-ui/core)와 스낵바 패키지(notistack)를 설치

$ yarn add @material-ui/core notistack

(스낵바 패키지는 material 에서 제공해주는 걸로 사용해도 되지만, 편의를 위해 따로 라이브러리를 사용하였다.)

 

index.js에서 스낵바 프로바이더를 설정

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { SnackbarProvider } from 'notistack';

ReactDOM.render(
    <SnackbarProvider maxSnack={3}>
        <App/>
    </SnackbarProvider>,
    document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

App.js 에서 컴포넌트 구성

import { useState, useEffect } from 'react';
import {
    FormGroup,
    FormControlLabel,
    Checkbox,
    Chip,
    makeStyles
} from '@material-ui/core';
import { useSnackbar } from 'notistack';

const useStyles = makeStyles((theme) => ({
    chip: {
        margin: theme.spacing(0.5),
    },
}));

const ChipTest = ({ label }) => {
    const classes = useStyles();
    const { enqueueSnackbar } = useSnackbar();

    useEffect(() => {
        return () => {
            enqueueSnackbar(`${label} clean up`, { variant: 'success' });
        }
    }, []);
    return (
        <Chip
            label={label}
            className={classes.chip}
        />
    );
};

export default function App() {
    const [arrChip, setArrChip] = useState([]);

    const handleChange = (event) => {
    };

    return (
        <>
            <FormGroup>
                <FormControlLabel
                    control={
                        <Checkbox
                            onChange={handleChange}
                            name="test1"
                            color="primary"
                        />
                    }
                    label="테스트1"
                />
                <FormControlLabel
                    control={
                        <Checkbox
                            onChange={handleChange}
                            name="test2"
                            color="primary"
                        />
                    }
                    label="테스트2"
                />
                <FormControlLabel
                    control={
                        <Checkbox
                            onChange={handleChange}
                            name="test3"
                            color="primary"
                        />
                    }
                    label="테스트3"
                />
                <FormControlLabel
                    control={
                        <Checkbox
                            onChange={handleChange}
                            name="test4"
                            color="primary"
                        />
                    }
                    label="테스트4"
                />
            </FormGroup>
            {arrChip.map((ch, idx) => (
                ch && <ChipTest key={idx} label={ch}/>
            ))}
        </>
    );
}

체크박스 4개를 구성

여기서 체크박스를 체크했을 때(onChange), arrChip의 상태값에 배열을 쌓아주면 배열(Chip)이 렌더링된다.

    const handleChange = (event) => {
        const isChecked = event.currentTarget.checked;
        const name = event.target.name;

        if (isChecked) {
            setArrChip([...arrChip, name]);
        }
    };

배열 요소를 없애는 방법 또한 간단하다. handleChange 함수 (onChange 이벤트) 에서 체크 해제한 요소를 제외한 배열을 재구성 한 뒤 setState 해주면 된다.

    const handleChange = (event) => {
        const isChecked = event.currentTarget.checked;
        const name = event.target.name;

        if (isChecked) {
            setArrChip([...arrChip, name]);
        } else {
            setArrChip(arrChip.filter(e => e !== name));
        }
    };

간단해보이지만 여기에는 약간의 함정이 있다. 스낵바를 통해 Chip 컴포넌트에서 cleanup 시 label을 뿌려보면 내가 체크 해제하는 체크박스에 대한 Chip 컴포넌트가 아닌 배열에 쌓인 순서대로 cleanup 되고 있다는것을 알 수 있다.

 

여기서 나는 테스트1 -> 테스트2 -> 테스트3 를 체크 한뒤 다시 테스트1 -> 테스트2 -> 테스트3 을 체크 해제 하였다.

스낵바는 아래에서 부터 시작된다.

위 사진과 같이 첫번째로 실행된 cleanup은 테스트 1이 아닌 테스트 3 이었다.(배열 마지막에 쌓인 요소)

 

필자는 그 이유가 자바스크립트의 배열객체의 index 때문이라고 생각한다. (정확하진않음.. 그저 내 생각)

개발자 모드로 배열을 찍어보면 test1 객체가 빠졌지만 2번째의 index(마지막요소)가 사라진 것을 확인할 수 있다.

 

이 문제를 해결하기 위해 임시로 체크해제한 요소에 undefined를 넣었다.

    const handleChange = (event) => {
        const isChecked = event.currentTarget.checked;
        const name = event.target.name;

        if (isChecked) {
            setArrChip([...arrChip, name]);
        } else {
            const newArrChip = arrChip.map(e => {
                if (e === name) {
                    return undefined
                } else {
                    return e;
                }
            });

            setArrChip(newArrChip.filter(e => e).length ===0 ? [] : newArrChip);
        }
    };

그리고 배열의 요소가 전부 undefined 일 때는 빈 배열로 초기화 하는 방법으로 해결 하였다.

 

위 내용에 대한 전체 소스는 깃허브에 올려놓았다.

https://github.com/sbjang123456/react-array-cleanup

 

sbjang123456/react-array-cleanup

배열로 추가된 컴포넌트에 대한 cleanup 테스트 프로젝트. Contribute to sbjang123456/react-array-cleanup development by creating an account on GitHub.

github.com

 

혹시 더 좋은 해결 방법이 있다면 댓글로 공유 부탁드립니다^^

반응형