[React Native] recoil 사용 시 Cannot delete property 오류

Jayden ·2023년 12월 28일

다음과 같이 모달이 오픈 되었을때, 항목을 클릭하면 recoil 상태에 추가하고,
선택이 된 것을 클릭하면 다시 선택해제 하도록 구현하였습니다.

SignupStep2.tsx


return (
  ...

<View style={[{ height : 90}, styles.jobList]}>
          <GenreItem width={30} height={30}  genreName="액션" xmlUrl={action} genreIndex={0}/>
          <GenreItem width={30} height={30}  genreName="롤플레잉" xmlUrl={rolePlaying}  genreIndex={1}/>
          <GenreItem width={30} height={30}  genreName="슈팅" xmlUrl={shooting}  genreIndex={2}/>
          <GenreItem width={30} height={30}  genreName="어드벤처" xmlUrl={adventure}  genreIndex={3} />
          <GenreItem width={30} height={30}  genreName="스포츠" xmlUrl={sports} genreIndex={4} />
</View>
<View style={[{ height : 90}, styles.jobList]}>
          <GenreItem width={30} height={30}  genreName="경주" xmlUrl={racing}  genreIndex={5}/>
          <GenreItem width={30} height={30}  genreName="퍼즐·퀴즈" xmlUrl={puzzle}  genreIndex={6}/>
          <GenreItem width={30} height={30}  genreName="캐주얼" xmlUrl={casual}  genreIndex={7}/>
          <GenreItem width={30} height={30}  genreName="보드" xmlUrl={board}  genreIndex={8}/>
          <GenreItem width={30} height={30}  genreName="아케이드" xmlUrl={arcade}  genreIndex={9}/>
</View>
<View style={[{ height : 90}, styles.jobList]}>
          <GenreItem width={30} height={30} genreName="음악" xmlUrl={music}  genreIndex={10}/>
          <GenreItem width={30} height={30} genreName="전략" xmlUrl={strategy}  genreIndex={11}/>
          <GenreItem width={30} height={30} genreName="시뮬레이션" xmlUrl={simulation}  genreIndex={12}/>
          <GenreItem width={30} height={30} genreName="교육" xmlUrl={education}  genreIndex={13}/>
  
 </View>
 ...
 )

store.ts

import { RecoilState, atom } from 'recoil';

type SignupType = {
 
  agenre : number[];
  ...
  
};

export const signupState: RecoilState<SignupType> = atom({
  key: 'signup',
  default: {
    agenre: [] as number[],
    ...
  },
}) as RecoilState<SignupType>;

GenreItem.tsx

선택된 항목을 recoil 상태 signupInfo의 agenre 배열에 추가합니다.

만일, findIndex() 메서드로 선택이 해당항목(장르)가 선택되었을 경우는 목록에서 제거하도록 (spice() 메서드 사용) 구현하였습니다.

export default function GenreItem({xmlUrl, width, height, genreName, genreIndex} : 
                                  {xmlUrl : string, width : number, height : number, genreName: string, genreIndex : number}) {
  
    const [signupInfo, setSignupInfo] = useRecoilState(signupState)
  
    return (
    <TouchableOpacity onPress={() => {
               const findIndex = signupInfo.agenre.indexOf(genreIndex);
                if(findIndex > -1) {
                  setSignupInfo({...signupInfo, agenre : signupInfo.agenre.splice(findIndex, 1)});
                  return;
                }
                setSignupInfo({...signupInfo, agenre : [...signupInfo.agenre, genreIndex]})}
           }
          <SvgXml xml={xmlUrl} width={width} height={height}/>
        </View>
        <Text style={{fontSize : 13}}>{genreName}</Text>
    </TouchableOpacity>
  );
}

하지만 다음과 같은 오류가 발생하였습니다.

useState나, recoil을 포함한 리액트 환경에서는 불변성(Immutablity)을 지키며 상태값을 설정해 주어야 한다는 것을 잠시 잊고 있었습니다.
signupInfo.agenre의 배열을 직접 변경하면 안되고, 새로운 배열을 만들어 그 배열로 새로운 상태값을 설정해 주어야 합니다.

const updatedAgenre = [...signupInfo.agenre]

import React from 'react'
import { Picker } from '@react-native-picker/picker';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack'
import { StatusBar } from 'expo-status-bar';
import { useEffect , useState } from 'react';
import { Button, TextInput ,Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import SplashScreen from 'react-native-splash-screen';
import { useRecoilState } from 'recoil';
import { signupState } from '../../store';
import {SvgXml, SvgUri} from 'react-native-svg';


export default function GenreItem({xmlUrl, width, height, genreName, genreIndex} : 
                                  {xmlUrl : any, width : number, height : number, genreName: string, genreIndex : number}) {
  
    const [signupInfo, setSignupInfo] = useRecoilState(signupState)
  
    return (
    <TouchableOpacity onPress={() => {
            const findIndex = signupInfo.agenre.indexOf(genreIndex);
                        
            if (findIndex > -1) {
                 const updatedAgenre = [...signupInfo.agenre];
                 updatedAgenre.splice(findIndex, 1);
                 setSignupInfo({ ...signupInfo, agenre: updatedAgenre });
                } else {
                      if(signupInfo.agenre.length === 3)
                      return;
                      setSignupInfo({ ...signupInfo, agenre: [...signupInfo.agenre, genreIndex] });
                        }
                      }
                    }
           <SvgXml xml={xmlUrl} width={width} height={height}/>
        <Text style={{fontSize : 13}}>{genreName}</Text>
    </TouchableOpacity>
  );
}


profile
프론트엔드 개발자

0개의 댓글