본문 바로가기

프로그래밍/React, React-Native

버튼 클릭 시 버튼 색 바꾸기 - State 업데이트를 잘못한 경우

버튼을 클릭할때 마다 버튼 색깔이 변하도록 만들어 보자.

 

useState를 사용하여 isSelect라는 변수를 false 로 초기화 한 후 button을 클릭할 때마다 isSelect 의 상태를 변경시켜주면된다.

 

이 단순해 보이는 작업에서 isSelect의 상태를 올바르게 업데이트 하지 않은 경우 아무런 동작이 일어나지 않는 것 처럼 보일 수 있는데, 오늘은 이 잘못 업데이트 하는 경우와 바르게 업데이트 하는 경우(배열형태와 딕셔너리 형태)를 비교하여 볼 예정이다.

 

원하는 결과를 미리보면 아래와 같다.

 

 


 

1. 문제 코드

useState 를 사용하여 버튼의 갯수만큼 false 배열을 초기화해 준다.

const [isSelect, setSelect] = useState([false, false, false]);

 

이후 getButton 이라는 함수를 만들어 버튼의 id(초기화한 배열의 인덱스)를 파라미터로 정의해 아래와 같은 함수를 만든다.

→ tempSelect 라는 변수에 isSelect 를 불러와 argument로 받은 id의 값에 따라 해당 인덱스에 원래의 반대 값을 넣어주는 코드이다.(tempSelect[id] 자리를 boolean 값을 변경해 isSelect 에 넣어준다.)

  const getButton = (id: number) => {
    return (
      <Pressable
        style={[
          styles.buttonContainer,
          {backgroundColor: isSelect[id] ? 'green' : 'yellow'},
        ]}
        onPress={() => {
          let tempSelect = isSelect;
          tempSelect[id] = !tempSelect[id];
          setSelect(tempSelect);
        }}>
        <Text>Color Change Button</Text>
      </Pressable>
    );
  };

 

그러나 이는 동작하지 않는다.


 

이는 state를 올바르게 업데이트 해주지 않았기 때문에 발생하는 문제이다.

 

javascript 에서는 변수에 객체를 저장했을 때 실제 값을 저장하는 것이 아닌 주소 값을 저장한다. 실제로 react 에서 상태가 변경되었을 때는 '얕은 비교'를 통해 상태를 비교하는데, 즉 실제 값이 아닌 참조 값을 비교한다. 여기서는 isSelect 의 실제 값은 변경되었지만, 실제로 isSelect의 참조값이 업데이트 되지 않았기 버튼의 색이 변경되지 않았다.

 

해당 문제를 해결하기 위해서 spread 문법을 이용해 아래와 같이 고칠 수 있다.

const getButton = (id: number) => {
    return (
      <Pressable
        style={[
          styles.buttonContainer,
          {backgroundColor: isSelect[id] ? 'green' : 'yellow'},
        ]}
        onPress={() => {
          setSelect([
            ...isSelect.slice(0, id),
            !isSelect[id],
            ...isSelect.slice(id + 1),
          ]);
        }}>
        <Text>Color Change Button</Text>
      </Pressable>
    );
};

 

추가로 딕셔너리 형태의 자료형은 변수 형태의 객체 키를 가져올때 어려움이 있었는데, [id]: 상태값 형태로 값을 업데이트 할 수 있었다.

 

같은 내용을 딕셔너리 형태의 자료형으로 변경한 코드는 아래와 같다.

const [isSelect, setSelect] = useState<any>({
    id1: false,
    id2: false,
    id3: false,
  });

const getButton = (id: string) => {
    return (
      <Pressable
        style={[
          styles.buttonContainer,
          {backgroundColor: isSelect[id] ? 'green' : 'yellow'},
        ]}
        onPress={() => {
          setSelect({...isSelect, [id]: !isSelect[id]});
        }}>
        <Text>Color Change Button</Text>
      </Pressable>
    );
};

 

 

코드 전문은 아래와 같다.

import React, {useState} from 'react';
import {Pressable, SafeAreaView, StyleSheet, Text} from 'react-native';

const Button_Dictionary = () => {
  const [isSelect, setSelect] = useState([false, false, false]);

  const getButton = (id: number) => {
    return (
      <Pressable
        style={[
          styles.buttonContainer,
          {backgroundColor: isSelect[id] ? 'green' : 'yellow'},
        ]}
        onPress={() => {
          setSelect([
            ...isSelect.slice(0, id),
            !isSelect[id],
            ...isSelect.slice(id + 1),
          ]);
        }}>
        <Text>Color Change Button</Text>
      </Pressable>
    );
  };

  return (
    <SafeAreaView style={styles.Container}>
      {getButton(0)}
      {getButton(1)}
      {getButton(2)}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  Container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttonContainer: {
    justifyContent: 'center',
    alignItems: 'center',
    width: 200,
    height: 50,
    borderRadius: 30,
    marginBottom: 15,
  },
});

export default Button_Dictionary;