New Recipe
Authentication with Supabase
Published on April 12th, 2024 by Nick Morgan (@morganick)
View recipeLatest Ignite Release
View on GithubProven 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.
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} /> ...
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!
Freshly added to the cookbook
Authentication with Supabase
Published on April 12th, 2024 by Nick Morgan (@morganick)
Extracting Apollo Client's Cache in Reactotron
Published on March 26th, 2024 by Frank Calise
PowerSync and Supabase for Local-First Data Management
Published on March 22nd, 2024 by Trevor Coleman
Enforcing JS/TS Import Order
Published on February 29th, 2024 by Mark Rickert
View all recipes