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}/>
))}
</>
);
}
여기서 체크박스를 체크했을 때(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
혹시 더 좋은 해결 방법이 있다면 댓글로 공유 부탁드립니다^^
'React' 카테고리의 다른 글
React + Redux 초기 셋팅 및 테스트 (1) | 2021.06.09 |
---|---|
React + Openlayers 구성 ( By React Context ) (0) | 2021.05.31 |
React(+material-ui) draggable + resizing modal 컴포넌트 (0) | 2021.05.22 |
[React] Keyboard 이벤트 (Enter Key 검색) (0) | 2021.05.21 |
[React] react-router-dom 시작하기 (feat. material-dashboard) (0) | 2021.04.01 |