2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

記事投稿キャンペーン 「2024年!初アウトプットをしよう」

TextInputコンポーネントのonChangeText()の入力値をuseRef()で管理したい方へ

Posted at

今回の目的

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属性がなくても画面上に表示されているのが驚きであり新しく発見したため)
バージョンが新しくなった際に、以上の対応では解決しない恐れもあるため、こちらでも随時確認していく。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?