[React] 고차함수 Map에서 별개로 동작하는 이벤트 적용하기

방충림·2023년 3월 25일
3

Catch the error

목록 보기
3/7
post-thumbnail

현상

React에서 map으로 펼쳐놓은 각각의 JSX요소에 각각에 이벤트를 적용해주고 싶을 때까 있다.

이럴 때 보통 해당 요소의 onClick과 같은 이벤트를 사용하여, 구현을 하게되는데 무심코 코딩을 하다보면 다음과 같은 상황을 마주하게된다.



원인

모든 요소가 같은 상태값을 참조하고 있기 때문에, 여러 버튼 중 한 버튼만 클릭이 되더라도 모든 요소가 다 반응하는 것이다.

해당 문제를 발생 시키는 코드는 아래와 같다.

const [colorPickerVisible, setColorPickerVisible] = useState(false);
...
<Box
                display='flex'
                flexDirection='column'
                gap={2}
                width='95%'
                margin='auto'
              >
                {categoryList.slice(1).map((option,index) => (
                  <Box key={index} value={option.value}>
                    <Box
                      display='flex'
                      alignItems='center'
                      justifyContent='space-between'
                    >
                      <Paper
                        sx={{
                          width: "10px",
                          height: "10px",
                          backgroundColor: option.value,
                        }}
                      ></Paper>
                      <Typography sx={{ flex: 1 }}>
                        &nbsp;&nbsp;{option.label}
                      </Typography>
                      <Box
                        position='relative'
                        display='flex'
                        alignItems='center'
                      >
                        //핵심 구간 시작
                        <Avatar
                          alt='colorPicker'
                          src={colorPicker}
                          sx={{
                            height: "20px",
                            width: "20px",
                            cursor: "pointer",
                            "&:hover": {
                              transform: "scale(1.2)",
                            },
                            // position: "absolute",
                            top: "0",
                            left: "0",
                          }}
                          onClick={() => setColorPickerVisible((prev) => !prev)}
                        />
                        <SketchPicker
                          color={option.value}
                          className={
                            colorPickerVisible
                              ? "colorPickerVisible"
                              : "colorPickerInvisible"
                          }
                        />
                        //핵심 구간 끝
                        <DeleteIcon
                          sx={{
                            color: "darkGrey",
                            marginLeft: "10px",
                            cursor: "pointer",
                            "&:hover": {
                              transform: "scale(1.2)",
                              // outline: "none",
                            },
                          }}
                        />
                      </Box>
                    </Box>
                  </Box>
                ))}
              </Box>

이를 해결하기 위해서는 어떻게 해야할까?

해결 방안

colorPickerVisible 상태를 배열로 관리하고, 각 Avatar 컴포넌트의 인덱스를 해당 상태 배열에서 참조하여 특정 SketchPicker만 보이도록 변경하면 된다.

이런식으로 말이다.


  const [categoryList, setCategoryList] = useState([
    { id: 0, value: "#fff", label: "전체" },
    { id: 1, value: "#9DC08B", label: "개인" },
    { id: 2, value: "#40513B", label: "직장" },
    { id: 3, value: "#609966", label: "가족" },
    { id: 4, value: "#719192", label: "생일 및 기념일" },
  ]);

const [colorPickerVisible, setColorPickerVisible] = useState(
  categoryList.slice(1).map(() => false)
);

<Box>
  {categoryList.slice(1).map((option, index) => (
    <Box key={index} value={option.value}>
      <Box>
        <Avatar
          onClick={() => {
            const newVisible = [...colorPickerVisible];
            newVisible[index] = !newVisible[index];
            setColorPickerVisible(newVisible);
          }}
        />
        <SketchPicker
          color={option.value}
          className={
            colorPickerVisible[index] ? "colorPickerVisible" : "colorPickerInvisible"
          }
        />
      </Box>
    </Box>
  ))}
</Box>

코드를 풀어서 한번 봐보자.

먼저 선언된 변수를 먼저 보면

1. 변수 선언부

useState(
  categoryList.slice(1).map(() => false)
);

categoryList에서 첫번째 요소를 제외한 배열의 개수만큼 false값을 맵해주고 입니다.

colorPickerVisible의 결과를 출력해본다면 [false, false, false, false] 가 나올것이다.

2. 이벤트 연결부

다음 의 onClick 이벤트 부분을 보자.

 <Avatar
        onClick={() => {
          const newVisible = [...colorPickerVisible];
          newVisible[index] = !newVisible[index];
          setColorPickerVisible(newVisible);
        }}
      />

먼저 colorPickerVisible를 스프레드 연산자로 새로운 배열로 만들어, newVisible이라는 변수에 할당한다.

그 다음 내가 컨택한 요소의 index값을 참고하여 배열을 해당 요소에 접근한다.
그 요소는 현재 역시나 false 일 것이다.

그것을 !false 해준 후 newVisible[index]에 할당한다.

이 시점에서 newVisible을 출력해보면
[false, true, false, false] 이런 상태일 것이다.

그리고 그 값을 setColorPickerVisible에 넣어 상태을 변경해준다.

3. CSS연결부 ( 삼항연산자 )

 <SketchPicker
    color={option.value}
    className={
        colorPickerVisible[index] ? "colorPickerVisible" : "colorPickerInvisible"
          }
        />

className에 조건문을 이렇게 걸어놓으면, 해당 요소는 해당 요소의 인덱스만을 참조하기 때문에, true바 바뀐 녀석만 이벤트가 실행되게 되는 것이다.

결과

아주 잘 동작한다.

profile
최선이 반복되면 최고가 된다.

0개의 댓글