組件設計
一般在設計組件時, 大部分的資訊都會由父組件透過 props 傳遞給子組件, 偶而也會有子組件中有觸發信號需要回傳給父組件 (ex: 子組件中包含了按鈕, 點擊後回傳觸發事件給父組件)
子組件types.ts
import React from 'react'; export type ValidatorProps = { regex: RegExp; alertMsg: string; }; export type TextInputModalProps = { isVisible: boolean; headerTitle: string; unit?: string; minValue?: string; maxValue?: string; maxLength?: string; iniValue?: string; validator?: ValidatorProps; setIsVisible: React.Dispatch<React.SetStateAction<boolean>>; setInputValue: React.Dispatch<React.SetStateAction<string>>; };
子組件的 .tsx
import { styles } from './styles'; import { TextInputModalProps } from './types'; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const TextInputModal = ({ isVisible = false, headerTitle, unit, minValue, maxValue, maxLength, iniValue, validator, setIsVisible, setInputValue, }: TextInputModalProps) => { const [input, setInput] = useState<string>(''); const [errorMsg, setErrorMsg] = useState(''); const [errorStatus, setErrorStatus] = useState(false); const [placeholder, setPlaceHolder] = useState(''); const handleInputChange: InputProps['onChangeText'] = (val) => { setInput(val); }; const handleSubmit = () => { if (input.length === 0) { setErrorStatus(true); setErrorMsg('Input cannot be empty.'); } else if (validator !== undefined) { if (!input.match(validator.regex)) { setErrorStatus(true); setErrorMsg(validator.alertMsg); } else { saveInput(); } } else if (minValue !== undefined && maxValue !== undefined) { if (Number(input) < Number(minValue) || Number(input) > Number(maxValue)) { setErrorStatus(true); setErrorMsg(headerTitle + ' must be between ' + minValue + ' ~ ' + maxValue); } else { saveInput(); } } else if (maxLength !== undefined) { if (input.length > Number(maxLength)) { setErrorStatus(true); setErrorMsg('Input length cannot exceed ' + maxLength + ' characters.'); } else { saveInput(); } } }; const saveInput = () => { setInputValue(input.trimStart()); setIsVisible(false); setErrorStatus(false); setInput(''); setErrorMsg(''); }; const initModal = () => { if (iniValue !== undefined) { setInput(iniValue); } else { setInput(''); } setErrorMsg(''); setErrorStatus(false); }; useEffect(() => { if (minValue !== undefined) { setPlaceHolder('Please enter ' + minValue + ' ~ ' + maxValue); } else if (maxLength !== undefined) { setPlaceHolder('Please enter no more than ' + maxLength + ' characters'); } else { setPlaceHolder('Please enter ' + headerTitle); } }, []); return ( <Modal isVisible={isVisible} onModalShow={initModal} onSwipeComplete={() => setIsVisible(false)} onBackdropPress={() => setIsVisible(false)} avoidKeyboard swipeDirection={['down']} propagateSwipe style={styles.modal} > <View style={styles.headerContainer}> <AntDesign name="close" size={24} color="#333333" onPress={() => setIsVisible(false)} style={styles.closeButton} /> <Text style={styles.headerTitle}>{headerTitle}</Text> <Button type="clear" containerStyle={styles.saveButton} titleStyle={styles.buttonTitleStyle} title="Save" onPress={handleSubmit} /> </View> <View style={styles.wrapper}> <View style={styles.inputContainer}> <Input inputStyle={styles.inputStyle} inputContainerStyle={errorStatus ? styles.inputErrorStatusContainerStyle : styles.inputContainerStyle} containerStyle={styles.groupCenter} errorStyle={styles.errorMessageStyle} autoFocus placeholder={placeholder} onChangeText={handleInputChange} value={input} autoCapitalize="none" errorMessage={errorMsg} keyboardType={Platform.OS === 'ios' ? 'numbers-and-punctuation' : 'number-pad'} /> </View> <Text style={styles.unit}>{unit}</Text> </View> </Modal> ); }; export default TextInputModal;