今回の目的
React Native上でのTextInput
コンポーネントにおいて、onChangeText()
の入力値をuseState()
で管理していたが、入力するたびに再レンダリングしてしまうため、再レンダリング抑制のためにuseRef()
を用いて管理したい。
前提条件(環境)
{
"name": "XXXXXXXXXXXX",
"version": "1.0.0",
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"@expo/vector-icons": "^13.0.0",
"@react-native-async-storage/async-storage": "1.18.2",
"expo": "~49.0.15",
"expo-constants": "~14.4.2",
"expo-font": "~11.4.0",
"expo-linking": "~5.0.2",
"expo-module-scripts": "^3.1.0",
"expo-router": "^2.0.0",
"expo-status-bar": "~1.6.0",
"firebase": "^10.7.1",
"react": "18.2.0",
"react-native": "0.72.6",
"react-native-gesture-handler": "~2.12.0",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@stylistic/eslint-plugin": "^1.5.3",
"@types/react": "~18.2.14",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"eslint": "^8.56.0",
"eslint-config-standard-with-typescript": "^43.0.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.33.2",
"typescript": "^5.3.3"
},
"private": true
}
作成したソースコード
SubmitButton
を押下するまでの再レンダリング抑制が目的のため、一部処理を目隠し目的で修正しております。
【改修前】
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
} from "react-native";
import { SubmitButton } from "../../components/SubmitButton";
import { Link, router } from "expo-router";
import { useState } from "react";
type SignUpProps = {
email: string;
password: string;
};
const SignUp = (): JSX.Element => {
const { container, inner, title, input, footer, footerLink, footerText } =
styles;
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const handlePress = (props: SignUpProps): void => {
const { email, password } = props;
// 会員登録
console.log(email, password);
router.push("memo/List");
};
return (
<View style={container}>
<View style={inner}>
<Text style={title}>Sign Up</Text>
<TextInput
style={input}
value={email}
autoCapitalize="none"
keyboardType="email-address"
placeholder="Email Address"
textContentType="emailAddress"
onChangeText={(text: string) => {
setEmail(text);
}}
/>
<TextInput
style={input}
value={password}
autoCapitalize="none"
secureTextEntry
placeholder="Password"
textContentType="password"
onChangeText={(text: string) => {
setPassword(text);
}}
/>
<SubmitButton
displayLabelValue="Submit"
onPress={() => {
handlePress({
email: emailRef.current,
password: passwordRef.current,
});
}}
/>
<View style={footer}>
<Text style={footerText}>Already registered?</Text>
{/* Linkタブに子要素を含めるためにはasChildが必須 */}
<Link href="auth/Login" asChild replace>
<TouchableOpacity>
<Text style={footerLink}>Log In</Text>
</TouchableOpacity>
</Link>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f0f4f8",
},
inner: {
paddingVertical: 24,
paddingHorizontal: 27,
},
title: {
fontSize: 24,
lineHeight: 32,
fontWeight: "bold",
marginBottom: 24,
},
input: {
borderWidth: 1,
borderColor: "#dddddd",
backgroundColor: "#ffffff",
height: 48,
padding: 8,
fontSize: 16,
marginBottom: 16,
},
footer: {
flexDirection: "row",
},
footerText: {
fontSize: 14,
lineHeight: 24,
marginRight: 8,
color: "#000000",
},
footerLink: {
fontSize: 14,
lineHeight: 24,
color: "#467fd3",
},
});
export default SignUp;
【改修後】
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
} from "react-native";
import { SubmitButton } from "../../components/SubmitButton";
import { Link, router } from "expo-router";
import { useRef } from "react";
type SignUpProps = {
email: string;
password: string;
};
const SignUp = (): JSX.Element => {
const { container, inner, title, input, footer, footerLink, footerText } =
styles;
const emailRef = useRef<string>("");
const passwordRef = useRef<string>("");
const handlePress = (props: SignUpProps): void => {
const { email, password } = props;
// 会員登録
console.log(email, password);
router.push("memo/List");
};
return (
<View style={container}>
<View style={inner}>
<Text style={title}>Sign Up</Text>
<TextInput
style={input}
value={emailRef.current}
autoCapitalize="none"
keyboardType="email-address"
placeholder="Email Address"
textContentType="emailAddress"
onChangeText={(text: string) => {
emailRef.current = text;
}}
/>
<TextInput
style={input}
value={passwordRef.current}
autoCapitalize="none"
secureTextEntry
placeholder="Password"
textContentType="password"
onChangeText={(text: string) => {
passwordRef.current = text;
}}
/>
<SubmitButton
displayLabelValue="Submit"
onPress={() => {
handlePress({
email: emailRef.current,
password: passwordRef.current,
});
}}
/>
<View style={footer}>
<Text style={footerText}>Already registered?</Text>
{/* Linkタブに子要素を含めるためにはasChildが必須 */}
<Link href="auth/Login" asChild replace>
<TouchableOpacity>
<Text style={footerLink}>Log In</Text>
</TouchableOpacity>
</Link>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f0f4f8",
},
inner: {
paddingVertical: 24,
paddingHorizontal: 27,
},
title: {
fontSize: 24,
lineHeight: 32,
fontWeight: "bold",
marginBottom: 24,
},
input: {
borderWidth: 1,
borderColor: "#dddddd",
backgroundColor: "#ffffff",
height: 48,
padding: 8,
fontSize: 16,
marginBottom: 16,
},
footer: {
flexDirection: "row",
},
footerText: {
fontSize: 14,
lineHeight: 24,
marginRight: 8,
color: "#000000",
},
footerLink: {
fontSize: 14,
lineHeight: 24,
color: "#467fd3",
},
});
export default SignUp;
発生した問題
画面上に表示されているTextInput
コンポーネント部分に値を入力しても、「入力した値」しか反映されなくなってしまった。
(例)"abc"を入力すると"abc"と値が保持、表示されているはずが、最後の入力である"c"のみしか値が保持されず、表示もされなくなってしまった。
試したこと
TextInput
コンポーネントにRef属性を追加し、内部から入力されている値を取得しようとした。
解決方法
TextInput
コンポーネントにvalue属性を追加していたが、それを削除したところ意図した入力値の保持、表示が確認できた。
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
} from "react-native";
import { SubmitButton } from "../../components/SubmitButton";
import { Link, router } from "expo-router";
import { useRef } from "react";
type SignUpProps = {
email: string;
password: string;
};
const SignUp = (): JSX.Element => {
const { container, inner, title, input, footer, footerLink, footerText } =
styles;
const emailRef = useRef<string>("");
const passwordRef = useRef<string>("");
const handlePress = (props: SignUpProps): void => {
const { email, password } = props;
// 会員登録
console.log(email, password);
router.push("memo/List");
};
return (
<View style={container}>
<View style={inner}>
<Text style={title}>Sign Up</Text>
<TextInput
style={input}
autoCapitalize="none"
keyboardType="email-address"
placeholder="Email Address"
textContentType="emailAddress"
onChangeText={(text: string) => {
emailRef.current = text;
}}
/>
<TextInput
style={input}
autoCapitalize="none"
secureTextEntry
placeholder="Password"
textContentType="password"
onChangeText={(text: string) => {
passwordRef.current = text;
}}
/>
<SubmitButton
displayLabelValue="Submit"
onPress={() => {
handlePress({
email: emailRef.current,
password: passwordRef.current,
});
}}
/>
<View style={footer}>
<Text style={footerText}>Already registered?</Text>
{/* Linkタブに子要素を含めるためにはasChildが必須 */}
<Link href="auth/Login" asChild replace>
<TouchableOpacity>
<Text style={footerLink}>Log In</Text>
</TouchableOpacity>
</Link>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f0f4f8",
},
inner: {
paddingVertical: 24,
paddingHorizontal: 27,
},
title: {
fontSize: 24,
lineHeight: 32,
fontWeight: "bold",
marginBottom: 24,
},
input: {
borderWidth: 1,
borderColor: "#dddddd",
backgroundColor: "#ffffff",
height: 48,
padding: 8,
fontSize: 16,
marginBottom: 16,
},
footer: {
flexDirection: "row",
},
footerText: {
fontSize: 14,
lineHeight: 24,
marginRight: 8,
color: "#000000",
},
footerLink: {
fontSize: 14,
lineHeight: 24,
color: "#467fd3",
},
});
export default SignUp;
補足
今回はTextInput
コンポーネントのvalue属性を削除したが、現環境(バージョン)下での解決方法である可能性もある。(value属性がなくても画面上に表示されているのが驚きであり新しく発見したため)
バージョンが新しくなった際に、以上の対応では解決しない恐れもあるため、こちらでも随時確認していく。