다음과 같이 모달이 오픈 되었을때, 항목을 클릭하면 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>
);
}