맛집 앱 만들기 프로젝트 Part11 폼 사용성 개선하기
InputField를 수정하여 ForwardRef 사용하기
[matzip/front/src/components/InputField.tsx]
지난 시간까지 로그인, 회원가입 페이지를 구현하였는데 사용자 편의성을 위해 이메일을 입력후 엔터나 다음버튼을 누르면 비밀번호 입력창으로 자동으로 포커스가되면 좋겠다는 생각을 했다..
그러기 위해서는 React 컴포넌트에 ref
prop을 넘겨서 그 내부에 있는 HTML 엘리먼트에 접근을 하게 해주는 forwardRef()
함수를 사용해야한다는것을 알게되어 지난번 구현해둔 InputField 컴포넌트에 코드를 추가하고 common이라는 ref를 합쳐주는 파일을 작성하였다
[matzip/front/src/utils/common.ts]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {ForwardedRef} from 'react';
function mergeRefs<T>(...refs: ForwardedRef<T>[]) {
return (node: T) => {
refs.forEach(ref => {
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
ref.current = node;
}
});
};
}
export {mergeRefs};
[matzip/front/src/components/InputField.tsx]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import React, {ForwardedRef, forwardRef, useRef} from 'react';
import {
Dimensions,
StyleSheet,
TextInput,
View,
TextInputProps,
Text,
Pressable,
} from 'react-native';
import {colors} from '../constants';
import {mergeRefs} from '../utils';
interface InputFieldProps extends TextInputProps {
disabled?: boolean;
error?: string;
touched?: boolean;
}
const deviceHeight = Dimensions.get('screen').height;
// 함수로되어있엇던 InputField를 화살표함수로 바꾸어주고 forwardRef를 호출한다
const InputField = forwardRef(
(
{disabled = false, error, touched, ...props}: InputFieldProps,
ref?: ForwardedRef<TextInput>,
) => {
const innerRef = useRef<TextInput | null>(null);
const handlePressInput = () => {
innerRef.current?.focus();
};
return (
<Pressable onPress={handlePressInput}>
<View
style={[
styles.container,
disabled && styles.disabled,
touched && Boolean(error) && styles.inputError,
]}>
<TextInput
ref={ref ? mergeRefs(innerRef, ref) : innerRef}
editable={!disabled}
placeholderTextColor={colors.GRAY_500}
style={[styles.input, disabled && styles.disabled]}
autoCapitalize="none"
spellCheck={false}
autoCorrect={false}
{...props}
/>
{touched && Boolean(error) && (
<Text style={styles.error}>{error}</Text>
)}
</View>
</Pressable>
);
},
);
const styles = StyleSheet.create({
container: {
borderWidth: 1,
borderColor: colors.GRAY_200,
padding: deviceHeight > 700 ? 15 : 10,
},
input: {
fontSize: 16,
color: colors.BLACK,
padding: 0,
},
disabled: {
backgroundColor: colors.GRAY_200,
color: colors.GRAY_700,
},
inputError: {
borderWidth: 1,
borderColor: colors.RED_300,
},
error: {
color: colors.RED_500,
fontSize: 12,
paddingTop: 5,
},
});
export default InputField;
회원가입 페이지 개선하기
[matzip/front/src/screens/auth/SignupScreen.tsx]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import React, {useRef} from 'react';
import {SafeAreaView, StyleSheet, Text, TextInput, View} from 'react-native';
import InputField from '../../components/InputField';
import useForm from '../../hooks/useForm';
import CustomButton from '../../components/CustomButton';
import {validateSignup} from '../../utils';
// 로그인에서 구현한 부분과 같이 useForm이라는 만들어둔 훅을 사용하여 데이터를 관리하고
// passwordConfirm이라는 새로운 속성을 추가하여 해당 속성은 password 재확인을 위해 사용한다
function SignupScreen() {
const passwordRef = useRef<TextInput | null>(null);
const passwordConfirmRef = useRef<TextInput | null>(null);
const signup = useForm({
initialValue: {
email: '',
password: '',
passwordConfirm: '',
},
validate: validateSignup,
});
const handleSubmit = () => {
console.log(signup.values);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.inputContainer}>
<InputField
// autoFocus를 주어 해당 페이지에 들어왔을때 여기에 포커스가되도록 설정
autoFocus
placeholder="이메일"
error={signup.errors.email}
touched={signup.touched.email}
inputMode="email"
// 키보드에 다음 버튼을 만들기위해 returnKeyType 옵션을 켜줌
returnKeyType="next"
//키보드가 닫히지 않도록 blurOnSubmit을 false로 줌
blurOnSubmit={false}
// next나 다음 버튼을 키보드에서 눌렀을때 다음 input창으로 넘어가기 위한 옵션추가
onSubmitEditing={() => passwordRef.current?.focus()}
{...signup.getTextInputProps('email')}
/>
<InputField
ref={passwordRef}
placeholder="비밀번호"
// 비밀번호 경고창 띄우지 않도록
textContentType="oneTimeCode"
error={signup.errors.password}
touched={signup.touched.password}
secureTextEntry
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => passwordConfirmRef.current?.focus()}
{...signup.getTextInputProps('password')}
/>
<InputField
ref={passwordConfirmRef}
placeholder="비밀번호 확인"
error={signup.errors.passwordConfirm}
touched={signup.touched.passwordConfirm}
secureTextEntry
onSubmitEditing={handleSubmit}
{...signup.getTextInputProps('passwordConfirm')}
/>
</View>
<CustomButton label="회원가입" onPress={handleSubmit} />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
margin: 30,
},
inputContainer: {
gap: 20,
marginBottom: 30,
},
});
export default SignupScreen;
로그인 페이지 개선하기
[matzip/front/src/screens/auth/LoginScreen.tsx]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import React, {useRef} from 'react';
import {SafeAreaView, StyleSheet, TextInput, View} from 'react-native';
import InputField from '../../components/InputField';
import CustomButton from '../../components/CustomButton';
import useForm from '../../hooks/useForm';
import {validateLogin} from '../../utils';
function LoginScreen() {
const passwordRef = useRef<TextInput | null>(null);
const login = useForm({
initialValue: {email: '', password: ''},
validate: validateLogin,
});
const handleSubmit = () => {
console.log('login.values', login.values);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.inputContainer}>
<InputField
autoFocus
placeholder="이메일"
error={login.errors.email}
touched={login.touched.email}
inputMode="email"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => passwordRef.current?.focus()}
{...login.getTextInputProps('email')}
/>
<InputField
ref={passwordRef}
placeholder="비밀번호"
error={login.errors.password}
touched={login.touched.password}
secureTextEntry
returnKeyType="join"
blurOnSubmit={false}
onSubmitEditing={handleSubmit}
{...login.getTextInputProps('password')}
/>
</View>
<CustomButton
label="로그인"
variant="filled"
size="large"
onPress={handleSubmit}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
margin: 30,
},
inputContainer: {
gap: 20,
marginBottom: 30,
},
});
export default LoginScreen;
폼 개선을 통해 자동으로 다음 포커스로 이동하는 앱 화면
This post is licensed under
CC BY 4.0
by the author.