Skip to main content

New Recipe

Authentication with Supabase

Published on April 12th, 2024 by Nick Morgan (@morganick)

View recipe

Latest Ignite Release

View on Github

Proven Recipes for your React Native apps

Starting from scratch doesn’t always make sense. That’s why we made the Ignite Cookbook for React Native – an easy way for developers to browse and share code snippets (or “recipes”) that actually work.

Spin Up Your App In Record Time

Stop reinventing the wheel on every project. Use the Ignite CLI to get your app started. Then, hop over to the Ignite Cookbook for React Native to browse for things like libraries in “cookie cutter” templates that work for almost any project. It’s the fastest way to get a React Native app off the ground.

Animation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation Image

Find Quality Code When You Need It

The popular forum sites are great for finding code until you realize it’s based on an old version of React Native. Ignite Cookbook is a place for recipes that work as of the time they’re published – meaning, it worked when it was posted. And if it ever goes out of date, we’ll make sure the community knows on what version it was last working.

bunx ignite-cli@latest new AuthRecipe --workflow=cng --remove-demo --git --install-deps --packager=bun

cd AuthRecipe
bun run ios

bunx ignite-cli@latest generate screen SignIn

import React, { FC, useState } from "react"
import { observer } from "mobx-react-lite"
import {
  Image,
  ImageStyle,
  Pressable,
  TextStyle,
  View,
  ViewStyle,
} from "react-native"
import { AppStackScreenProps } from "app/navigators"
import { Button, Screen, Text, TextField } from "app/components"
import { useSafeAreaInsetsStyle } from "app/utils/useSafeAreaInsetsStyle"
import { colors, spacing } from "app/theme"

const logo = require("../../assets/images/logo.png")

interface SignInScreenProps extends AppStackScreenProps<"SignIn"> {}

export const SignInScreen: FC<SignInScreenProps> = observer(
  function SignInScreen() {
    const $bottomContainerInsets = useSafeAreaInsetsStyle(["bottom"])
    const [email, setEmail] = useState("")
    const [password, setPassword] = useState("")

    const onSignIn = () => {
      // Sign In Flow
      console.log("Sign In Flow", { email, password })
    }

    const onSignUp = () => {
      // Sign Up Flow
      console.log("Sign Up Flow")
    }

    const onForgotPassword = () => {
      // Forgot Password Flow
      console.log("Forgot Password Flow")
    }

    return (
      <Screen
        contentContainerStyle={$root}
        preset="auto"
        safeAreaEdges={["top"]}
      >
        <View style={$container}>
          <View style={$topContainer}>
            <Image style={$logo} source={logo} resizeMode="contain" />
          </View>
          <View style={[$bottomContainer, $bottomContainerInsets]}>
            <View>
              <TextField
                containerStyle={$textField}
                label="Email"
                autoCapitalize="none"
                defaultValue={email}
                onChangeText={setEmail}
              />
              <TextField
                containerStyle={$textField}
                label="Password"
                autoCapitalize="none"
                defaultValue={password}
                secureTextEntry
                onChangeText={setPassword}
              />
            </View>
            <View>
              <Button onPress={onSignIn}>Sign In</Button>
              <Pressable style={$forgotPassword} onPress={onForgotPassword}>
                <Text preset="bold">Forgot Password?</Text>
              </Pressable>
              <Text style={$buttonDivider}>- or -</Text>
              <Button preset="reversed" onPress={onSignUp}>
                Sign Up
              </Button>
            </View>
            <View style={$cap} />
          </View>
        </View>
      </Screen>
    )
  }
)

const $root: ViewStyle = {
  minHeight: "100%",
  backgroundColor: colors.palette.neutral100,
}

const $container: ViewStyle = {
  backgroundColor: colors.background,
}

const $topContainer: ViewStyle = {
  height: 200,
  justifyContent: "center",
  alignItems: "center",
}

const $bottomContainer: ViewStyle = {
  backgroundColor: colors.palette.neutral100,
  paddingBottom: spacing.xl,
  paddingHorizontal: spacing.lg,
}

const $cap: ViewStyle = {
  backgroundColor: colors.palette.neutral100,
  borderTopLeftRadius: 16,
  borderTopRightRadius: 16,
  height: spacing.xl,
  position: "absolute",
  top: -spacing.xl,
  left: 0,
  right: 0,
}

const $textField: ViewStyle = {
  marginBottom: spacing.md,
}

const $forgotPassword: ViewStyle = {
  marginVertical: spacing.md,
}

const $buttonDivider: TextStyle = {
  textAlign: "center",
  marginVertical: spacing.md,
}

const $logo: ImageStyle = {
  height: 88,
  width: "100%",
  marginBottom: spacing.xxl,
}

const AppStack = observer(function AppStack() {
  // success-line
  const isAuthenticated = false
  return (
    <Stack.Navigator
      screenOptions={{
        headerShown: false,
        navigationBarColor: colors.background,
      }}
    >
      // success-line-start
      {isAuthenticated ? (
        <>
          {/** 🔥 Your screens go here */}
          <Stack.Screen name="Welcome" component={Screens.WelcomeScreen} />
          {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */}
        </>
      ) : (
        <Stack.Screen name="SignIn" component={Screens.SignInScreen} />
      )}
      // success-line-end
    </Stack.Navigator>
  )
})

EXPO_PUBLIC_SUPABASE_URL="https://<your-project-id>.supabase.co"
EXPO_PUBLIC_SUPABASE_ANON_KEY="<your-anon-public-key>"

.env

bunx eas secret:push --scope project --env-file .env

export interface ConfigBaseProps {
  persistNavigation: "always" | "dev" | "prod" | "never"
  catchErrors: "always" | "dev" | "prod" | "never"
  exitRoutes: string[]
  // success-line-start
  supabaseUrl: string
  supabaseAnonKey: string
  // success-line-end
}

export type PersistNavigationConfig = ConfigBaseProps["persistNavigation"]

const BaseConfig: ConfigBaseProps = {
  // This feature is particularly useful in development mode, but
  // can be used in production as well if you prefer.
  persistNavigation: "dev",

  /**
   * Only enable if we're catching errors in the right environment
   */
  catchErrors: "always",

  /**
   * This is a list of all the route names that will exit the app if the back button
   * is pressed while in that screen. Only affects Android.
   */
  exitRoutes: ["Welcome"],
  // success-line-start
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  supabaseUrl: process.env.EXPO_PUBLIC_SUPABASE_URL!,
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  supabaseAnonKey: process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
  // success-line-end
}

export default BaseConfig

bunx expo install @supabase/supabase-js react-native-mmkv

bun ios
# or
bun android

import { MMKV } from "react-native-mmkv"

const storage = new MMKV({
  id: "session",
})

// TODO: Remove this workaround for encryption: https://github.com/mrousavy/react-native-mmkv/issues/665
storage.set("workaround", true)

/**
 * A simple wrapper around MMKV that provides a base API
 * that matches AsyncStorage for use with Supabase.
 */

/**
 * Get an item from storage by key
 *
 * @param {string} key of the item to fetch
 * @returns {Promise<string | null>} value for the key as a string or null if not found
 */
export async function getItem(key: string): Promise<string | null> {
  try {
    return storage.getString(key) ?? null
  } catch {
    console.warn(`Failed to get key "${key}" from secure storage`)
    return null
  }
}

/**
 * Sets an item in storage by key
 *
 * @param {string} key of the item to store
 * @param {string} value of the item to store
 */
export async function setItem(key: string, value: string): Promise<void> {
  try {
    storage.set(key, value)
  } catch {
    console.warn(`Failed to set key "${key}" in secure storage`)
  }
}

/**
 * Removes a single item from storage by key
 *
 * @param {string} key of the item to remove
 */
export async function removeItem(key: string): Promise<void> {
  try {
    storage.delete(key)
  } catch {
    console.warn(`Failed to remove key "${key}" from secure storage`)
  }
}

bunx expo install expo-secure-store expo-crypto

...
    "plugins": [
      "expo-localization",
      // success-line
      "expo-secure-store",
      [
        "expo-build-properties",
        {
          "ios": {
            "newArchEnabled": false,
            "flipper": false
          },
          "android": {
            "newArchEnabled": false
          }
        }
      ],
      "expo-font"
    ],
...

bun ios
# or
bun android

import { MMKV } from "react-native-mmkv"
// success-line-start
import * as SecureStore from "expo-secure-store"
import * as Crypto from "expo-crypto"

const fetchOrGenerateEncryptionKey = (): string => {
  const encryptionKey = SecureStore.getItem("session-encryption-key")

  if (encryptionKey) {
    return encryptionKey
  } else {
    const uuid = Crypto.randomUUID()
    SecureStore.setItem("session-encryption-key", uuid)
    return uuid
  }
}
// success-line-end

const storage = new MMKV({
  id: "session",
  // success-line
  encryptionKey: fetchOrGenerateEncryptionKey(),
})

...

import Config from "app/config"
import { createClient } from "@supabase/supabase-js"
import * as SessionStorage from "app/utils/storage/SessionStorage"
import { AppState } from "react-native"

export const supabase = createClient(
  Config.supabaseUrl,
  Config.supabaseAnonKey,
  {
    auth: {
      storage: SessionStorage,
      autoRefreshToken: true,
      detectSessionInUrl: false,
    },
  }
)

export { type Session, type AuthError } from "@supabase/supabase-js"

/**
 * Tells Supabase to autorefresh the session while the application
 * is in the foreground. (Docs: https://supabase.com/docs/reference/javascript/auth-startautorefresh)
 */
AppState.addEventListener("change", (nextAppState) => {
  if (nextAppState === "active") {
    supabase.auth.startAutoRefresh()
  } else {
    supabase.auth.stopAutoRefresh()
  }
})

import React, {
  createContext,
  PropsWithChildren,
  useCallback,
  useContext,
  useState,
} from "react"
import { Session, supabase } from "./supabase"
import { AuthResponse, AuthTokenResponsePassword } from "@supabase/supabase-js"

type AuthState = {
  isAuthenticated: boolean
  token?: Session["access_token"]
}

type SignInProps = {
  email: string
  password: string
}

type SignUpProps = {
  email: string
  password: string
}

type AuthContextType = {
  signIn: (props: SignInProps) => Promise<AuthTokenResponsePassword>
  signUp: (props: SignUpProps) => Promise<AuthResponse>
} & AuthState

const AuthContext = createContext<AuthContextType>({
  isAuthenticated: false,
  token: undefined,
  signIn: () => new Promise(() => ({})),
  signUp: () => new Promise(() => ({})),
})

export function useAuth() {
  const value = useContext(AuthContext)

  if (process.env.NODE_ENV !== "production") {
    if (!value) {
      throw new Error("useAuth must be used within an AuthProvider")
    }
  }

  return value
}

export const AuthProvider = ({ children }: PropsWithChildren) => {
  const [token, setToken] = useState<AuthState["token"]>(undefined)

  const signIn = useCallback(
    async ({ email, password }: SignInProps) => {
      const result = await supabase.auth.signInWithPassword({
        email,
        password,
      })

      if (result.data?.session?.access_token) {
        setToken(result.data.session.access_token)
      }

      return result
    },
    [supabase]
  )

  const signUp = useCallback(
    async ({ email, password }: SignUpProps) => {
      const result = await supabase.auth.signUp({
        email,
        password,
      })

      if (result.data?.session?.access_token) {
        setToken(result.data.session.access_token)
      }

      return result
    },
    [supabase]
  )

  return (
    <AuthContext.Provider
      value={{
        isAuthenticated: !!token,
        token,
        signIn,
        signUp,
      }}
    >
      {children}
    </AuthContext.Provider>
  )
}

...
import { ViewStyle } from "react-native"
// success-line
import { AuthProvider } from "./services/auth/useAuth"

...

  return (
    // success-line
    <AuthProvider>
      <SafeAreaProvider initialMetrics={initialWindowMetrics}>
        <ErrorBoundary catchErrors={Config.catchErrors}>
          <GestureHandlerRootView style={$container}>
            <AppNavigator
              linking={linking}
              initialState={initialNavigationState}
              onStateChange={onNavigationStateChange}
            />
          </GestureHandlerRootView>
        </ErrorBoundary>
      </SafeAreaProvider>
    // success-line
    </AuthProvider>
  )
}

...

import { colors } from "app/theme"
// success-line
import { useAuth } from "app/services/auth/useAuth"

...

const AppStack = observer(function AppStack() {
  // error-line
  const isAuthenticated = false
  // success-line
  const { isAuthenticated } = useAuth()
  return (
    <Stack.Navigator screenOptions={{ headerShown: false, navigationBarColor: colors.background }}>
      {isAuthenticated ? (
        <>
          {/** 🔥 Your screens go here */}
          <Stack.Screen name="Welcome" component={Screens.WelcomeScreen} />
          {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */}
        </>
      ) : (
        <Stack.Screen name="SignIn" component={Screens.SignInScreen} />
      )}
    </Stack.Navigator>
  )
})

...

...
import { colors, spacing } from "app/theme"
// success-line
import { useAuth } from "app/services/auth/useAuth"

...
export const SignInScreen: FC<SignInScreenProps> = observer(function SignInScreen() {
  const $bottomContainerInsets = useSafeAreaInsetsStyle(["bottom"])
  // success-line
  const { signIn, signUp } = useAuth()
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")

  const passwordInput = React.useRef<TextInput>(null)

  const onSignIn = () => {
    // error-line-start
    // Sign In Flow
    console.log("Sign In Flow", { email, password })
    // error-line-end
    // success-line
    signIn({ email, password })
  }

  const onSignUp = () => {
    // error-line-start
    // Sign Up Flow
    console.log("Sign Up Flow")
    // error-line-end
    // success-line
    signUp({ email, password })
  }

...

...
type AuthContextType = {
  signIn: (props: SignInProps) => Promise<AuthTokenResponsePassword>
  signUp: (props: SignUpProps) => Promise<AuthResponse>
  // success-line
  signOut: () => void
} & AuthState

const AuthContext = createContext<AuthContextType>({
  isAuthenticated: false,
  token: undefined,
  signIn: () => new Promise(() => ({})),
  signUp: () => new Promise(() => ({})),
  // success-line
  signOut: () => undefined,
})
...
export const AuthProvider = ({ children }: PropsWithChildren) => {
...
  // success-line-start
  const signOut = useCallback(async () => {
    await supabase.auth.signOut()
    setToken(undefined)
  }, [supabase])
  // success-line-end

  return (
    <AuthContext.Provider
      value={{
        isAuthenticated: !!token,
        token,
        signIn,
        signUp,
        // success-line
        signOut,
      }}
    >
      {children}
    </AuthContext.Provider>
  )
}

import { observer } from "mobx-react-lite"
import React, { FC } from "react"
import { Image, ImageStyle, TextStyle, View, ViewStyle } from "react-native"
// error-line
import { Text } from "app/components"
// success-line
import { Button, Text } from "app/components"
import { isRTL } from "../i18n"
import { AppStackScreenProps } from "../navigators"
import { colors, spacing } from "../theme"
import { useSafeAreaInsetsStyle } from "../utils/useSafeAreaInsetsStyle"
// success-line
import { useAuth } from "app/services/auth/useAuth"

const welcomeLogo = require("../../assets/images/logo.png")
const welcomeFace = require("../../assets/images/welcome-face.png")

interface WelcomeScreenProps extends AppStackScreenProps<"Welcome"> {}

export const WelcomeScreen: FC<WelcomeScreenProps> = observer(
  function WelcomeScreen() {
    const $bottomContainerInsets = useSafeAreaInsetsStyle(["bottom"])
    const { signOut } = useAuth()

    return (
      <View style={$container}>
        <View style={$topContainer}>
          <Image
            style={$welcomeLogo}
            source={welcomeLogo}
            resizeMode="contain"
          />
          <Text
            testID="welcome-heading"
            style={$welcomeHeading}
            // error-line
            tx="welcomeScreen.readyForLaunch"
            // success-line
            text="Congratulations 🎉 You're signed in!"
            preset="heading"
          />
          <Text tx="welcomeScreen.exciting" preset="subheading" />
          <Image
            style={$welcomeFace}
            source={welcomeFace}
            resizeMode="contain"
          />
        </View>
        <View style={[$bottomContainer, $bottomContainerInsets]}>
          // error-line
          <Text tx="welcomeScreen.postscript" size="md" />
          // success-line
          <Button onPress={signOut}>Sign Out</Button>
        </View>
      </View>
    )
  }
)

...
const AppStack = observer(function AppStack() {
  const { isAuthenticated } = useAuth()
  return (
    <Stack.Navigator screenOptions={{ headerShown: false, navigationBarColor: colors.background }}>
      {isAuthenticated ? (
        <>
          {/** 🔥 Your screens go here */}
          <Stack.Screen name="Welcome" component={Screens.WelcomeScreen} />
          {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */}
        </>
      ) : (
        <Stack.Screen
          name="SignIn"
          component={Screens.SignInScreen}
          // success-line
          options={{ animationTypeForReplace: "pop" }}
        />
      )}
    </Stack.Navigator>
  )
})
...

...
// error-line
import React, { createContext, PropsWithChildren, useCallback, useContext, useState } from "react"
// success-line
import React, { createContext, PropsWithChildren, useCallback, useContext, useEffect, useState } from "react"
...
export const AuthProvider = ({ children }: PropsWithChildren) => {
  const [token, setToken] = useState<AuthState["token"]>(undefined)

  // success-line-start
  useEffect(() => {
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange((event, session) => {
      switch (event) {
        case "SIGNED_OUT":
          setToken(undefined)
          break
        case "INITIAL_SESSION":
        case "SIGNED_IN":
        case "TOKEN_REFRESHED":
          setToken(session?.access_token)
          break
        default:
        // no-op
      }
    })

    return () => {
      subscription.unsubscribe()
    }
  }, [supabase])
  // success-line-end
...

...
export const SignInScreen: FC<SignInScreenProps> = observer(function SignInScreen() {
  const $bottomContainerInsets = useSafeAreaInsetsStyle(["bottom"])
  const { signIn, signUp } = useAuth()
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  // success-line
  const [isSigningIn, setIsSigningIn] = useState(false)

  // error-line-start
  const onSignIn = () => {
    signIn({ email, password })
  // error-line-end
  // success-line-start
  const onSignIn = async () => {
    try {
      setIsSigningIn(true)
      await signIn({ email, password })
    } finally {
      setIsSigningIn(false)
    }
  // success-line-end
  }
  ...
            // error-line
            <Button onPress={onSignIn}>Sign In</Button>
            // success-line-start
            <Button onPress={onSignIn}>
              {isSigningIn ? "Signing In..." : "Sign In"}
            </Button>
            // success-line-end
            ...


...
export const SignInScreen: FC<SignInScreenProps> = observer(function SignInScreen() {
  const $bottomContainerInsets = useSafeAreaInsetsStyle(["bottom"])
  const { signIn, signUp } = useAuth()
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [isSigningIn, setIsSigningIn] = useState(false)
  // success-line
  const [isSigningUp, setIsSigningUp] = useState(false)

  const onSignIn = async () => {
    try {
      setIsSigningIn(true)
      await signIn({ email, password })
    } finally {
      setIsSigningIn(false)
    }
  }

  // error-line-start
  const onSignUp = () => {
    signUp({ email, password })
  // error-line-end
  // success-line-start
  const onSignUp = async () => {
    try {
      setIsSigningUp(true)
      await signUp({ email, password })
    } finally {
      setIsSigningUp(false)
    }
  // success-line-end
  }
  ...
            // error-line
            <Button preset="reversed" onPress={onSignUp}>Sign Up</Button>
            // success-line-start
            <Button preset="reversed" onPress={onSignUp}>
              {isSigningUp ? "Signing Up..." : "Sign Up"}
            </Button>
            // success-line-end
            ...


...
export const SignInScreen: FC<SignInScreenProps> = observer(function SignInScreen() {
  const $bottomContainerInsets = useSafeAreaInsetsStyle(["bottom"])
  const { signIn, signUp } = useAuth()
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [isSigningIn, setIsSigningIn] = useState(false)
  const [isSigningUp, setIsSigningUp] = useState(false)
  // success-line
  const isLoading = isSigningIn || isSigningUp
...
          <View>
            <TextField
              containerStyle={$textField}
              label="Email"
              autoCapitalize="none"
              defaultValue={email}
              onChangeText={setEmail}
              // success-line
              readOnly={isLoading}
            />
            <TextField
              containerStyle={$textField}
              label="Password"
              autoCapitalize="none"
              defaultValue={password}
              secureTextEntry
              onChangeText={setPassword}
              // success-line
              readOnly={isLoading}
            />
          </View>
          <View>
            // success-line
            <Button onPress={onSignIn} disabled={isLoading}>
              {isSigningIn ? "Signing In..." : "Sign In"}
            </Button>
            // success-line
            <Pressable style={$forgotPassword} onPress={onForgotPassword} disabled={isLoading}>
              <Text preset="bold">Forgot Password?</Text>
            </Pressable>
            <Text style={$buttonDivider}>- or -</Text>
            // success-line
            <Button preset="reversed" onPress={onSignUp} disabled={isLoading}>
              {isSigningUp ? "Signing Up..." : "Sign Up"}
            </Button>
          </View>
        </View>

...
export const SignInScreen: FC<SignInScreenProps> = observer(function SignInScreen() {
  const $bottomContainerInsets = useSafeAreaInsetsStyle(["bottom"])
  const { signIn, signUp } = useAuth()
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  // success-line
  const [error, setError] = useState<string | undefined>(undefined)
...
  const onSignIn = async () => {
    try {
      setIsSigningIn(true)
      // success-line
      setError(undefined)

      // error-line
      await signIn({ email, password })
      // success-line-start
      const { error } = await signIn({ email, password })
      if (error) {
        setError(error.message)
      }
      // success-line-end
    } finally {
      setIsSigningIn(false)
    }
  }

  const onSignUp = async () => {
    try {
      setIsSigningUp(true)
      // success-line
      setError(undefined)

      // error-line
      await signUp({ email, password })
      // success-line-start
      const { error } = await signUp({ email, password })
      if (error) {
        setError(error.message)
      }
      // success-line-end
    } finally {
      setIsSigningUp(false)
    }
  }
  ...
    return (
    <Screen
      contentContainerStyle={$root}
      preset="auto"
      safeAreaEdges={["top"]}
    >
      <View style={$container}>
        <View style={$topContainer}>
          <Image style={$logo} source={logo} resizeMode="contain" />
        </View>
        <View style={[$bottomContainer, $bottomContainerInsets]}>
          // success-line
          {error && <Text style={$errorText}>{error}</Text>}
          <View>
...

const $logo: ImageStyle = {
  height: 88,
  width: "100%",
  marginBottom: spacing.xxl,
}

// success-line-start
const $errorText: TextStyle = {
  color: colors.error,
}
// success-line-end


...
export const SignInScreen: FC<SignInScreenProps> = observer(function SignInScreen() {
  const $bottomContainerInsets = useSafeAreaInsetsStyle(["bottom"])
  const { signIn, signUp } = useAuth()
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  // success-line
  const [validationErrors, setValidationErrors] = useState<Map<string, string>>(new Map())
...
  // success-line-start
  const validateForm = () => {
    const errors: Map<string, string> = new Map()

    if (!email || email.split("@").length !== 2) {
      errors.set("Email", "must be valid email")
    }

    if (!password) {
      errors.set("Password", "cannot be blank")
    }

    return errors
  }
  // success-line-end

  const onSignIn = async () => {
    try {
      setIsSigningIn(true)
      setError(undefined)

      // success-line-start
      const errors = validateForm()
      setValidationErrors(errors)
      if (errors.size > 0) return
      // success-line-end

      const { error } = await signIn({ email, password })
      if (error) {
        setError(error.message)
      }
    } finally {
      setIsSigningIn(false)
    }
  }

  const onSignUp = async () => {
    try {
      setIsSigningUp(true)
      setError(undefined)

      // success-line-start
      const errors = validateForm()
      setValidationErrors(errors)
      if (errors.size > 0) return
      // success-line-end

      const { error } = await signUp({ email, password })
      if (error) {
        setError(error.message)
      }
    } finally {
      setIsSigningUp(false)
    }
  }
...
          <View>
            <TextField
              containerStyle={$textField}
              label="Email"
              autoCapitalize="none"
              defaultValue={email}
              onChangeText={setEmail}
              readOnly={isLoading}
              // success-line-start
              helper={validationErrors.get("Email")}
              status={validationErrors.get("Email") ? "error" : undefined}
              // success-line-end
            />
            <TextField
              containerStyle={$textField}
              label="Password"
              autoCapitalize="none"
              defaultValue={password}
              secureTextEntry
              onChangeText={setPassword}
              readOnly={isLoading}
              // success-line-start
              helper={validationErrors.get("Password")}
              status={validationErrors.get("Password") ? "error" : undefined}
              // success-line-end
            />
...

...
            <TextField
              autoCapitalize="none"
              // success-line-start
              autoComplete="email"
              autoCorrect={false}
              // success-line-end
              containerStyle={$textField}
              defaultValue={email}
              helper={validationErrors.get("Email")}
              // success-line
              inputMode="email"
              label="Email"
              onChangeText={setEmail}
              readOnly={isLoading}
              status={validationErrors.get("Email") ? "error" : undefined}
            />
             <TextField
              autoCapitalize="none"
              // success-line-start
              autoComplete="current-password"
              autoCorrect={false}
              // success-line-end
              containerStyle={$textField}
              defaultValue={password}
              helper={validationErrors.get("Password")}
              label="Password"
              onChangeText={setPassword}
              readOnly={isLoading}
              secureTextEntry
              status={validationErrors.get("Password") ? "error" : undefined}
            />
...

// error-line
import React, { FC, useState } from "react"
// success-line
import React, { FC, useRef, useState } from "react"
import { observer } from "mobx-react-lite"
// error-line
import { Image, ImageStyle, Pressable, TextStyle, View, ViewStyle } from "react-native"
// success-line
import { Image, ImageStyle, Pressable, TextInput, TextStyle, View, ViewStyle } from "react-native"
...
export const SignInScreen: FC<SignInScreenProps> = observer(function SignInScreen() {
  ...
  const isLoading = isSigningIn || isSigningUp

  // success-line
  const passwordInput = useRef<TextInput>(null)

  const onSignIn = async () => {
...
          <View>
            <TextField
              autoCapitalize="none"
              autoComplete="email"
              autoCorrect={false}
              containerStyle={$textField}
              defaultValue={email}
              helper={validationErrors.get("Email")}
              inputMode="email"
              label="Email"
              onChangeText={setEmail}
              // success-line
              onSubmitEditing={() => passwordInput.current?.focus()}
              readOnly={isLoading}
              // success-line
              returnKeyType="next"
              status={validationErrors.get("Email") ? "error" : undefined}
            />
            <TextField
              autoCapitalize="none"
              autoComplete="current-password"
              autoCorrect={false}
              containerStyle={$textField}
              defaultValue={password}
              helper={validationErrors.get("Password")}
              label="Password"
              onChangeText={setPassword}
              // success-line
              onSubmitEditing={onSignIn}
              readOnly={isLoading}
              ref={passwordInput}
              // success-line
              returnKeyType="done"
              secureTextEntry
              status={validationErrors.get("Password") ? "error" : undefined}
            />
          </View>

// error-line
import React, { ComponentType, FC, useRef, useState } from "react"
// success-line
import React, { ComponentType, FC, useMemo, useRef, useState } from "react"
import { observer } from "mobx-react-lite"
import {
  Image,
  ImageStyle,
  Pressable,
  TextInput,
  TextStyle,
  View,
  ViewStyle,
} from "react-native"
import { AppStackParamList, AppStackScreenProps } from "app/navigators"
// error-line
import { Button, Screen, Text, TextField } from "app/components"
// success-line-start
import {
  Button,
  Icon,
  Screen,
  Text,
  TextField,
  TextFieldAccessoryProps,
} from "app/components"
// success-line-end
...
export const SignInScreen: FC<SignInScreenProps> = observer(function SignInScreen() {
  ...
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  // success-line
  const [isPasswordHidden, setIsPasswordHidden] = useState(true)
  ...
  // success-line-start
  const PasswordRightAccessory: ComponentType<TextFieldAccessoryProps> = useMemo(
    () =>
      function PasswordRightAccessory(props: TextFieldAccessoryProps) {
        return (
          <Icon
            icon={isPasswordHidden ? "view" : "hidden"}
            color={colors.palette.neutral800}
            containerStyle={props.style}
            size={20}
            onPress={() => setIsPasswordHidden(!isPasswordHidden)}
          />
        )
      },
    [isPasswordHidden],
  )
  // success-line-end

  return (
    <Screen
      contentContainerStyle={$root}
      preset="auto"
      safeAreaEdges={["top"]}
    >
  ...
              <TextField
              autoCapitalize="none"
              autoComplete="current-password"
              autoCorrect={false}
              containerStyle={$textField}
              defaultValue={password}
              helper={validationErrors.get("Password")}
              labelTx="common.password"
              onChangeText={setPassword}
              onSubmitEditing={onSubmit}
              readOnly={isLoading}
              ref={passwordInput}
              returnKeyType="send"
              // error-line
              secureTextEntry
              // success-line-start
              RightAccessory={PasswordRightAccessory}
              secureTextEntry={isPasswordHidden}
              // success-line-end
              status={validationErrors.get("Password") ? "error" : undefined}
            />
  ...

updated 27 hours ago
Animation Image

Backed By A Community of React Native Experts

The Ignite Cookbook isn’t just a random group of code snippets. It’s a curated collection of usable code samples that the Infinite Red team’s used in their own React Native projects. Having worked with some of the biggest clients in the tech industry, we know a thing or two about keeping our code to a high standard. You can code confidently!

Animation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation ImageAnimation Image