javascriptreact-native

Unusual "Warning: Text strings must be rendered within a <Text> component." error, related to useState?


Here's the error I'm getting:

iOS Bundled 50ms node_modules/expo-router/entry.js (1 module)
 ERROR  Warning: Text strings must be rendered within a <Text> component.

   8 |
   9 | export default function SignInScreen() {
> 10 |   const [email, setEmail] = useState('');
     |                                     ^
  11 |   const [password, setPassword] = useState('');
  12 |   const [otp, setOtp] = useState('');
  13 |   const [showForgotPassword, setShowForgotPassword] = useState(false);

Call Stack
  SignInScreen (apps/rider_app/app/sign-in.tsx:10:37)
  NavigationRoot (apps/rider_app/app/_layout.tsx:15:47)
  KeyboardControllerView (<anonymous>)
  AuthProvider (apps/rider_app/contexts/AuthContext.tsx:98:40)
  RootLayout (apps/rider_app/app/_layout.tsx:41:28)
  RNCSafeAreaProvider (<anonymous>)
  App (<anonymous>)
  ErrorOverlay (<anonymous>)

I don't understand why it tells me about string having to be rendered within a Text component, then it points to a state declaration.

Here's the screen where I'm getting the error:

import React, { useState } from 'react';
import {
  SafeAreaView,
  Text,
  TextInput,
  TouchableOpacity,
  View
} from 'react-native';
import {
  KeyboardAwareScrollView,
  KeyboardToolbar
} from 'react-native-keyboard-controller';
import { Colors } from 'myplatform-ui';
import { globalStyles } from 'myplatform-ui';
import { useAuth } from '@/contexts/AuthContext';

export default function SignInScreen() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [otp, setOtp] = useState('');
  const [showForgotPassword, setShowForgotPassword] = useState(false);
  const [resetEmail, setResetEmail] = useState('');
  const {
    signIn,
    verifyOtp,
    sendPasswordResetOTP,
    authError,
    setAuthError
  } = useAuth();

  const handleSignIn = async () => {
    if (!email) {
      setAuthError(new Error('Please enter a valid email'));
      return;
    }
    if (!password) {
      setAuthError(new Error('Please enter a valid password'));
      return;
    }

    await signIn(email, password);
  };

  const handleOtpSignIn = async () => {
    if (!email) {
      setAuthError(new Error('Please enter a valid email'));
      return;
    }
    if (!otp) {
      setAuthError(new Error('Please enter a valid OTP'));
      return;
    }

    await verifyOtp(email, otp);
  };

  const handleSendResetOTP = async () => {
    if (!resetEmail) {
      setAuthError(new Error('Please enter a valid email'));
      return;
    }

    const { error } = await sendPasswordResetOTP(resetEmail);

    if (error) {
      setAuthError(error);
    } else {
      // Show success message
      setShowForgotPassword(false);
      setAuthError(
        new Error('Password reset OTP sent. Please check your inbox.')
      );
    }
  };

  return (
    <>
      <SafeAreaView style={{ flex: 1 }}>
        <KeyboardAwareScrollView
          style={{ flex: 1 }}
          // properties inherited from ScrollView:
          contentContainerStyle={{ flex: 1 }}
        >
          <View
            style={[
              globalStyles.mainContent,
              globalStyles.centeredContainer,
              { padding: 32 }
            ]}
          >
            <Text
              style={[
                {
                  fontSize: 24,
                  fontWeight: 'bold',
                  marginBottom: 10,
                  textAlign: 'center'
                }
              ]}
            >
              MyPlatform Rider App
            </Text>
            {!showForgotPassword && <>
              {/* Email Section */}
              <TextInput
                style={[
                  globalStyles.textInput,
                  globalStyles.formElement,
                  { marginBottom: 10 }
                ]}
                placeholder="Email"
                placeholderTextColor={Colors.grey300}
                value={email}
                onChangeText={(text) => {
                  setEmail(text);
                  setAuthError(null);
                }}
                autoCapitalize="none"
                keyboardType="email-address"
              />

              {/* Password Section */}
              <Text
                style={[
                  {
                    fontSize: 20,
                    fontWeight: 'bold',
                    marginBottom: 10,
                    textAlign: 'center'
                  }
                ]}
              >
                Password Sign In
              </Text>
              <TextInput
                style={[
                  globalStyles.textInput,
                  globalStyles.formElement,
                  { marginBottom: 10 }
                ]}
                placeholder="Password"
                placeholderTextColor={Colors.grey300}
                value={password}
                onChangeText={(text) => {
                  setPassword(text);
                  setAuthError(null);
                }}
                secureTextEntry
              />
              <TouchableOpacity
                style={[
                  globalStyles.button,
                  globalStyles.formElement,
                  { padding: 12 }
                ]}
                onPress={handleSignIn}
              >
                <Text style={globalStyles.buttonText}>Sign In</Text>
              </TouchableOpacity>
            </>}

            {/* toggle reset password view */}
            <TouchableOpacity
              style={{ marginTop: 8, alignSelf: 'center' }}
              onPress={() => {
                setShowForgotPassword(!showForgotPassword);
                setAuthError(null);
              }}
            >
              <Text style={{ color: Colors.primary, }}>
                {showForgotPassword
                  ? 'Cancel Password Reset'
                  : 'Forgot password?'
                }
              </Text>
            </TouchableOpacity>

            {!showForgotPassword && <>
              {/* OTP Section */}
              <Text
                style={[
                  {
                    fontSize: 20,
                    fontWeight: 'bold',
                    margin: 10,
                    textAlign: 'center'
                  }
                ]}
              >
                OTP Sign In
              </Text>
              <TextInput
                style={[
                  globalStyles.textInput,
                  globalStyles.formElement,
                  { marginBottom: 10 }
                ]}
                placeholder="One Time Password"
                placeholderTextColor={Colors.grey300}
                value={otp}
                onChangeText={(text) => {
                  setOtp(text);
                  setAuthError(null);
                }}
                keyboardType="number-pad"
              />
              <TouchableOpacity
                style={[
                  globalStyles.button,
                  globalStyles.formElement,
                  { padding: 12 }
                ]}
                onPress={handleOtpSignIn}
              >
                <Text style={globalStyles.buttonText}>Sign In via OTP</Text>
              </TouchableOpacity>

              {authError && (
                <Text
                  style={{
                    fontSize: 16,
                    color: 'red',
                    marginTop: 10,
                    textAlign: 'center'
                  }}
                >
                  {authError.message || 'An unknown error occurred'}
                </Text>
              )}
            </>}

            {/* Forgot Password Section */}
            {showForgotPassword && (
              <View
                style={[
                  {
                    marginTop: 16,
                    padding: 15,
                    backgroundColor: Colors.grey100,
                    borderRadius: 8
                  },
                  globalStyles.formElement,
                  globalStyles.cardShadow
                ]}
              >
                <Text
                  style={{
                    fontSize: 18,
                    fontWeight: 'bold',
                    marginBottom: 10,
                    textAlign: 'center'
                  }}
                >
                  Reset Password
                </Text>
                <TextInput
                  style={[
                    globalStyles.textInput,
                    globalStyles.formElement,
                    { marginBottom: 10 }
                  ]}
                  placeholder="Enter your email"
                  placeholderTextColor={Colors.grey300}
                  value={resetEmail}
                  onChangeText={(text) => {
                    setResetEmail(text);
                    setAuthError(null);
                  }}
                  autoCapitalize="none"
                  keyboardType="email-address"
                />
                <TouchableOpacity
                  style={[
                    globalStyles.button,
                    globalStyles.formElement,
                    { marginTop: 10 }
                  ]}
                  onPress={handleSendResetOTP}
                >
                  <Text style={globalStyles.buttonText}>Send OTP</Text>
                </TouchableOpacity>
              </View>
            )}
          </View>
        </KeyboardAwareScrollView>
      </SafeAreaView>
      <KeyboardToolbar /> {/* had to put outside of SafeAreaView for it to work:
   https://github.com/kirillzyusko/react-native-keyboard-controller/issues/415
      */}
    </>
  );
}

If I comment out everything related to email in the code, I get the same error but this time pointing at the opening parenthesis of the next (password) state.


Solution

  • The issue is not at the line you see. This error is coming from the JSX you are trying to return.
    You see line 10 because code must be minified.

    There could be many reasons for this error but here most likely it seems to be the comments:
    eg: {/* Forgot Password Section */}
    Or authError is sometimes an empty string.

    PS: For OP's case it is the comment.