리액트 네이티브에서 radio 형태의 컴포넌트를 구현하여야 하는 상황이 발행하였다.
따로 지원되는 라이브러리가 없으니 직접 구현하도록 하자!
제품상태 컴포넌트는 거래 컴포넌트와 유사한 구성을 가지므로 재사용이 가능하게 TextInput에 값을 여러가지 전달하기로 하였다.
세부적으로 구현해야 하는 기능은
<TextInput
multiline={isArea}
style={isArea ? styles.inputArea : styles.inputText}
value={value}
placeholder={placeholder}
onFocus={onPressHandler}
inputMode={inputType ? undefined : 'none'}
onChangeText={inputType ? onChangeHandler : () => {}} />
zustand를 이용하여 저장하고 value를 통해서 전달했다.// store.ts
interface MyProduct {
...
status: string;
}
interface ProductRegist {
product: MyProduct;
setProduct: (product: MyProduct) => void;
}
const useProductStore = create<ProductRegist>()(set => ({
product: {} as MyProduct,
setProduct: product => set(state => ({...state, product})),
}));
모달창은 하단에서 슬라이드로 올라오는 형태로 선택리스트를 보여주는 형태를 가진다. 다른 컴포넌트도 모달창을 사용하고 있어서 공통컴포넌트로 묶고자 하지만 내부 디자인이 달라서 우선적으로 제품상태에 대한 항목만 작성하였다.
세부적으로 구현해야 하는 기능은
// 데이터 받아오기
const [data, setData] = useState([]);
const {modalState, setModalState} = useProductModal();
let axiosUrl = '';
useLayoutEffect(() => {
axios
.get(axiosUrl)
.then(response => {
let newArray = response.data.map((d: productListData) => {
return {key: d.codeId, value: d.codeNm};
});
setData(newArray);
})
.catch(e => {
console.log(e);
});
}, []);
// modal구현
<Modal animationType={'slide'} visible={modalState} transparent={true}>
<Pressable
onPress={() => {
setModalState(false);
}}
style={styles.modalBackground}></Pressable>
<View style={styles.modal}>
<View style={styles.title}>
<Text style={styles.titleText}>제품 상태</Text>
</View>
<View style={styles.breadcrumb}>
<Text style={styles.breadcrumbText}>제품의 상태를 알려주세요</Text>
</View>
<MakeList data={data} />
</View>
</Modal>
modalState라는 boolean 타입의 useState로 구성하여 활용한다.modalState를 false로 바꿔준다. 이를 통해 모달창 닫기를 구현한다.codeId와 codeNm로 구성된 array로 리스트를 구성하였다.//StyleSheet
modal: {
marginBottom: 0,
marginTop: 'auto',
height: 570,
}
제품의 상태는 한 가지 항목을 필수로 선택하도록 하고 있다. 체크박스의 형태는 별도로 필요없으니 구현하지 않는다. 제품상태는 형태가 같은 컴포넌트이므로 FlatList를 활용하여서 구성하였다.
세부적으로 구현해야 하는 기능은
const [checkedNum, setCheckedNum] = useState<number>(-1);
const [checked, setChecked] = useState<string>('');
<FlatList
data={data}
ListFooterComponent={<ConfirmButton checked={checked} />}
renderItem={({item, index}) => {
let key = item.key ?? item.value ?? item;
return (
<Pressable
onPress={() => {
if (matchSelected(index)) deSelected();
else selected(index, key);
}}>
<View style={styles.list}>
<Text
style={
matchSelected(index)
? [styles.listText, styles.textActive]
: styles.listText
}>
{productStatusName(key)}
</Text>
<Text
style={
matchSelected(index)
? [styles.listTextDesc, styles.textActive]
: styles.listTextDesc
}>
{productStatusDesc(key)}
</Text>
</View>
</Pressable>
);
}}
/>
);
};
ListFooterComponent속성에 <ConfirmButton />컴포넌트를 리턴하였다.//ConfirmButton Component
const {setProduct, product} = useProductStore();
const {setModalState} = useProductModal();
return (
<TouchableOpacity
disabled={checked ? false : true}
style={checked ? [styles.button, styles.buttonActive] : styles.button}
onPress={() => {
if (checked) {
setProduct({...product, status: checked});
setModalState(false);
}
}}>
<Text style={styles.buttonText}>확인</Text>
</TouchableOpacity>
);
// store.ts
const useProductStore = create<ProductRegist>()(set => ({
product: {} as MyProduct,
setProduct: product => set(state => ({...state, product})),
}));
zustand로 설정한 Product의 status에 선택 된 항목을 전달한다.zustand로 설정한 ModalState의 값에 false를 전달하여 모달을 닫는다.모달창이 닫힌 후 포커스가 그대로 남아 있어서 onFocus가 동작하지 않는다.
그래서 동일한 동작에도 반복 될 수 있도록 onPressIn으로 변경하였다.
<View style={styles.inputContainer}>
<Text style={styles.inputTitle}>{label}</Text>
<TextInput
multiline={isArea}
style={isArea ? styles.inputArea : styles.inputText}
value={value}
placeholder={placeholder}
onPressIn={onPressHandler}
inputMode={inputType ? undefined: 'none'}
onChangeText={inputType ? onChangeHandler : () => {}}></TextInput>
</View>
모달창이 닫힌 후에 커서가 그대로 남아있지만 값은 value에 저장되어 수정되지 않는다.
