Щоб завершити процес автентифікації, нам потрібно реалізувати функцію виходу, яка припиняє сесію користувача та надає можливість вийти. Саме це ми і будемо робити в цій частині підручника.
Що далі?
У цьому розділі ми:
- Додамо кнопку виходу: Інтегруємо опцію для виходу в панель заголовка для зручного доступу.
- Налаштуємо вкладки навігації: Використаємо @expo/vector-icons для створення чистого та зрозумілого досвіду навігації.
- Очищення коду: Рефакторинг для збереження чистоти та масштабованості коду.
До кінця цього підручника ваш додаток матиме відшліфований процес автентифікації.
Підсумок коду
Крок 1: Знайдемо точку входу для групи вкладок
Точка входу для групи вкладок знаходиться в файлі (tabs)/_layout.tsx.
Це місце, де ми будемо змінювати макет, щоб перейти до навігації за допомогою вкладок та реалізувати панель заголовка.
Крок 2: Огляд поточного коду
На даний момент файл (tabs)/_layout.tsx має вигляд:
import { ThemedText } from "@/components/ThemedText";
import { Redirect, Stack } from "expo-router";
import { useSession } from "@/store/auth/auth-context";
export default function AppLayout() {
const { session, isLoading } = useSession();
// Ви можете залишити екран завантаження відкритим, або відобразити екран завантаження, як ми робимо тут.
if (isLoading) {
return Loading...;
}
// Потрібна лише автентифікація в макеті групи (app), оскільки користувачам
// потрібно мати доступ до групи (auth) і знову увійти.
if (!session) {
// На вебі статичний рендеринг зупиниться тут, оскільки користувач не автентифікований
// в процесі безголового Node, в якому рендеряться сторінки.
// #TODO: змінити перенаправлення на сторінку входу
return ;
}
// Цей макет може бути відкладений, оскільки це не головний макет.
return ;
}
Крок 3: Короткий огляд поточного коду
- Управління сесією: Код перевіряє, чи завантажується сесія або чи користувач автентифікований. Якщо сесії немає, користувач перенаправляється на сторінку входу.
- Рендеринг: Якщо сесія завантажується, відображається текст "завантаження", в наступному уроці ми побудуємо спінер завантаження. Якщо користувач не автентифікований, його перенаправляють.
Якщо сесія є дійсною, макет повертає стек-навігатор.
Пишемо код
Оновлення Colors.ts
Ми ще не використали найкращим чином вбудовані хуки тем в додатку, і саме це ми змінюватимемо в цьому уроці.
- У директорії constants знайдіть файл Colors.ts.
- Оновіть файл так, щоб основний колір був доданий до обох тем — світлої та темної.
/**
* Нижче наведені кольори, які використовуються в додатку. Кольори визначені в світлій та темній темах.
* Є багато інших способів стилізувати ваш додаток. Наприклад, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app) тощо.
*/
const tintColorLight = '#4CAF50';
const tintColorDark = '#fff';
export const Colors = {
light: {
text: '#11181C',
background: '#fff',
tint: tintColorLight,
icon: '#687076',
tabIconDefault: '#687076',
tabIconSelected: tintColorLight,
primary: '#4CAF50',
},
dark: {
text: '#ECEDEE',
background: '#151718',
tint: tintColorDark,
icon: '#9BA1A6',
tabIconDefault: '#9BA1A6',
tabIconSelected: tintColorDark,
primary: '#4CAF50',
},
};
Оновлення (tabs)_layout.tsx
Тепер давайте видалимо стек-навігатор, який повертається, і замість цього повернемо компонент Tabs.
- Компонент Tabs призначений для рендерингу навігаційних маршрутів через своїх дочірніх елементів Tabs.Screen.
- Він також приймає різні властивості, які дозволяють налаштувати і стилізувати як панель вкладок, так і панель заголовка відповідно до наших потреб.
Дотримуємося прикладу в коді нижче
- Імпортуємо компонент Tabs.
- Повертаємо компонент Tabs, який рендерить 2 маршрути.
Note: Ми не оновлювали маршрути вкладок, але ми відображаємо різні імена для двох маршрутів. - Додайте порожній об'єкт ScreenOptions.
import React from "react";
import { ThemedText } from "@/components/ThemedText";
import { Redirect, Tabs } from "expo-router";
import { useSession } from "@/store/auth/auth-context";
export default function TabLayout() {
const { session, isLoading, signOut } = useSession();
// Ви можете залишити екран завантаження відкритим, або показати екран завантаження, як ми це робимо тут.
if (isLoading) {
return Loading...;
}
if (!session) {
// На вебі статичний рендеринг зупиниться тут, оскільки користувач не автентифікований
// в безголовому процесі Node, в якому рендеряться сторінки.
return ;
}
return (
);
}
Додаємо Іконки до Вкладок
Тепер, коли ми маємо вкладки, давайте додамо до них іконки. Бібліотека @expo-vector-icons за замовчуванням встановлена, тому ми скористаємося саме цією бібліотекою.
- Імпортуємо: Імпортуємо бібліотеки іконок
2.
tabBarIcon: Використовуйте імпортовану бібліотеку для відображення іконки
import React from "react";
import FontAwesome from "@expo/vector-icons/FontAwesome";
import FontAwesome5 from "@expo/vector-icons/FontAwesome5";
import { Colors } from "@/constants/Colors";
import { useColorScheme } from "@/hooks/useColorScheme";
import { ThemedText } from "@/components/ThemedText";
import { Redirect, Tabs } from "expo-router";
import { useSession } from "@/store/auth/auth-context";
import { ms } from "react-native-size-matters";
export default function TabLayout() {
const colorScheme = useColorScheme();
const { session, isLoading, signOut } = useSession();
// Ви можете залишити екран завантаження відкритим або показати екран завантаження, як ми робимо тут.
if (isLoading) {
return Loading...;
}
if (!session) {
// На вебі статичне рендерингування зупиниться тут, оскільки користувач не аутентифікований
// в безголовому процесі Node, в якому рендеряться сторінки.
return ;
}
return (
(
),
}}
/>
(
),
}}
/>
);
}
Оскільки ми додали основний колір нашого додатку до об'єктів тем, ми тепер можемо використовувати useColorScheme для динамічного отримання відповідного кольору залежно від поточної теми.
Додавання ScreenOptions
У цьому розділі ми налаштуємо різні властивості screenOptions для конфігурації TabBar і HeaderBar.
Розбір використаних screenOptions:
1.
tabBarActivetIntColor: Ця властивість визначає колір для активної іконки та тексту вкладки. Вона використовує хук useColorScheme для динамічного налаштування кольору залежно від теми додатку (світла або темна тема).
2. tabBarButton: Ця властивість налаштовує поведінку кнопки на панелі вкладок. У цьому випадку ми використовуємо компонент HapticTab для надання індивідуального ефекту тактильного зворотного зв'язку при натисканні на вкладку.
3. tabBarBackground: Ця властивість встановлює користувацький фон для панелі вкладок.
4. tabBarStyle: Ця властивість дозволяє налаштувати стиль панелі вкладок. Ми використовуємо Platform.select(), щоб застосувати різні стилі для iOS та інших платформ:
- Для iOS використовується прозорий фон, а панель вкладок розташовується абсолютно для досягнення ефекту розмиття.
- Для інших платформ встановлюється стандартний фон і видаляються тіні та межа зверху.
5. headerShown: Ця властивість гарантує, що заголовок відображається вгорі екрану.
Це встановлено в значення true, що означає, що заголовок з'являтиметься на всіх екранах цього макету.
6. headerStyle: Ця властивість налаштовує стиль самого заголовка. Подібно до панелі вкладок, ми динамічно застосовуємо колір фону в залежності від кольорової схеми, а також видаляємо тінь та межу заголовка.
7. headerStatusBarHeight: Ця властивість встановлює висоту області статусної панелі в заголовку. Вона встановлена в 0
, що прибирає будь-який зайвий простір над заголовком (корисно для налаштування макету).
8. headerTitle: Ми видаляємо заголовок, залишаючи його порожнім.
9. headerRight: Ця властивість додає користувацьку кнопку на праву сторону заголовка. Тут ми використовуємо іконку шестерні, яка викликає функцію signOut при натисканні.
Згодом ця кнопка відкриє модальне вікно налаштувань, яке також міститиме посилання для виходу.
import React from "react";
import { Platform } from "react-native";
import { HapticTab } from "@/components/HapticTab";
import TabBarBackground from "@/components/ui/TabBarBackground";
import FontAwesome from "@expo/vector-icons/FontAwesome";
import FontAwesome5 from "@expo/vector-icons/FontAwesome5";
import { Colors } from "@/constants/Colors";
import { useColorScheme } from "@/hooks/useColorScheme";
import { ThemedText } from "@/components/ThemedText";
import { Redirect, Tabs } from "expo-router";
import { useSession } from "@/store/auth/auth-context";
import { ms } from "react-native-size-matters";
export default function TabLayout() {
const colorScheme = useColorScheme();
const { session, isLoading, signOut } = useSession();
// Ви можете залишити екран завантаження відкритим або показати екран завантаження, як це робимо ми тут.
if (isLoading) {
return Завантаження...;
}
if (!session) {
// На вебі статичне рендерингування зупиниться тут, оскільки користувач не автентифікований
// у безголовому процесі Node, в якому рендеряться сторінки.
return ;
}
return (
(
),
headerRightContainerStyle: {
paddingHorizontal: ms(10),
},
}}
>
(
),
}}
/>
(
),
}}
/>
);
}
Наш додаток тепер має повний процес автентифікації з функціями реєстрації (signUp), входу (signIn) та виходу (signOut), готовими до інтеграції з Firebase Auth.
Тестування
npx expo run:android
Процес реєстрації (Sign Up)
- Запустіть додаток і перейдіть на екран реєстрації.
- Введіть правильні дані та створіть новий акаунт.
- Переконайтеся, що ви перенаправлені в автентифіковану частину додатка.
Процес входу (Sign In)
- Вийдіть із системи, а потім спробуйте увійти знову.
Процес виходу (Sign Out)
- Перебуваючи в системі, натисніть кнопку виходу в хедері.
- Переконайтесь, що вас перенаправлено на екран входу (sign-in).
Обробка помилок
- Спробуйте зареєструватися або увійти з неправильними даними (наприклад, порожніми полями або некоректним форматом електронної пошти).
- Переконайтесь, що відображаються відповідні повідомлення про помилки.
Інтеграція теми
- Переключіть ваш пристрій між світлим і темним режимом.
- Перевірте, що тема додатка динамічно оновлюється для таб-бара та хедерів.
Навігація
- Переміщуйтесь між вкладками.
- Переконайтесь, що іконки та стилі відповідають активній колірній схемі.
Очищення коду
Є кілька речей, які можна очистити в коді, перш ніж переходити до інтеграції з Firebase, таких як невикористані імпорти, вбудовані стилі (inline CSS), зашиті кольори у форматі hex, а також ми забули оновити один компонент для використання бібліотеки react-native-size-matters.
Оновити _not-found.tsx
Ми забули перетворити StyleSheet на ScaledSheet з react-native-size-matters, тому давайте додамо це в очищення коду.
import React from "react";
import { Link, Stack } from "expo-router";
import { ScaledSheet } from "react-native-size-matters";
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
export default function NotFoundScreen() {
return (
<>
Цей екран не існує.
Перейти на головний екран!
);
}
const styles = ScaledSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
padding: "20@ms",
},
link: {
marginTop: "15@vs",
paddingVertical: "15@vs",
},
});
## Кольори
Оновіть компоненти Themed, щоб вони використовували константи з Colors.
**ThemedPressable**
import React, { ReactNode } from "react";
import { Platform } from "react-native";
import { PlatformPressable } from "@react-navigation/elements";
import { ScaledSheet } from "react-native-size-matters";
import * as Haptics from "expo-haptics";
import { Colors } from "@/constants/Colors";
interface ThemedPressableProps {
children: ReactNode;
}
export function ThemedPressable({
children,
style,
...props
}: ThemedPressableProps) {
return (
{
if (Platform.OS === "ios") {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}
props.onPressIn?.(ev);
}}
{children}
);
}
const styles = ScaledSheet.create({
default: {
alignItems: "center",
backgroundColor: Colors["light"].primary || Colors["dark"].primary,
borderRadius: "7.5@ms",
display: "flex",
flexDirection: "row",
justifyContent: "center",
paddingHorizontal: "15@s",
paddingVertical: "7.5@s",
textAlign: "center",
},
});
```
ThemedText
import { Text, type TextProps } from "react-native";
import { ScaledSheet } from "react-native-size-matters";
import { useThemeColor } from "@/hooks/useThemeColor";
import { Colors } from "@/constants/Colors";
export type ThemedTextProps = TextProps & {
darkColor?: string;
lightColor?: string;
type?: "default" | "defaultSemiBold" | "link" | "subtitle" | "title";
};
export function ThemedText({
darkColor,
lightColor,
style,
type = "default",
...rest
}: ThemedTextProps) {
const color = useThemeColor({ light: lightColor, dark: darkColor }, "text");
return (
type === "subtitle" ? styles.subtitle : undefined,
type === "title" ? styles.title : undefined,
style,
]}
{...rest}
/>
);
}
const styles = ScaledSheet.create({
default: {
fontSize: "16@s",
lineHeight: "24@ms",
},
defaultSemiBold: {
fontSize: "16@s",
fontWeight: "600",
lineHeight: "24@ms",
},
title: {
fontSize: "32@s",
fontWeight: "bold",
lineHeight: "32@s",
},
subtitle: {
fontSize: "20@s",
fontWeight: "bold",
},
link: {
color: Colors["light"].primary || Colors["dark"].primary,
fontSize: "16@s",
lineHeight: "24@ms",
},
});
ThemedTextInput
import React from "react";
import { Text, TextInput, View, type TextInputProps } from "react-native";
import { ScaledSheet } from "react-native-size-matters";
import { useThemeColor } from "@/hooks/useThemeColor";
import { Colors } from "@/constants/Colors";
export type ThemedTextInputProps = TextInputProps & {
ariaLabel?: string;
darkColor?: string;
icon?: React.ReactNode;
lightColor?: string;
tabIndex?: number;
validationError?: boolean;
validationErrorMessage?: string;
};
export function ThemedTextInput({
ariaLabel,
darkColor,
icon,
lightColor,
secureTextEntry,
tabIndex,
onChange,
onBlur,
onFocus,
onEndEditing,
validationError,
validationErrorMessage,
value,
...rest
}: ThemedTextInputProps) {
const backgroundColor = useThemeColor(
{ light: lightColor, dark: darkColor },
"background"
);
const placeholderColor = useThemeColor(
{ light: lightColor, dark: darkColor },
"tabIconDefault"
);
const color = useThemeColor({ light: lightColor, dark: darkColor }, "text");
return (
<>
onBlur={onBlur}
onFocus={onFocus}
onEndEditing={onEndEditing}
placeholderTextColor={placeholderColor}
secureTextEntry={secureTextEntry}
style={[styles.input, { color }]}
tabIndex={tabIndex}
value={value}
{...rest}
/>
{icon && {icon}}
{validationError && (
{validationErrorMessage ? validationErrorMessage : null}
)}
);
}
const styles = ScaledSheet.create({
container: {
alignItems: "center",
borderColor: Colors.light.primary ?? Colors.dark.primary,
borderRadius: "7.5@ms",
borderWidth: "1@ms",
flexDirection: "row",
paddingHorizontal: "16@ms",
},
input: {
flex: 1,
fontSize: "16@s",
lineHeight: "24@s",
paddingVertical: "8@ms",
},
"error-text": {
color: "#FF0000",
fontSize: "15@s",
lineHeight: "15@s",
padding: 0,
margin: 0,
},
});
Оновити SignUp
Again we are making use of our theming hooks and there were a few inline css rules that were moved to the styles object.
import React, { useEffect, useState } from "react";
import { ScrollView, useWindowDimensions } from "react-native";
import { ScaledSheet, ms } from "react-native-size-matters";
import { router } from "expo-router";
import AntDesign from "@expo/vector-icons/AntDesign";
import Feather from "@expo/vector-icons/Feather";
import { ThemedPressable } from "@/components/ThemedPressable";
import { ThemedText } from "@/components/ThemedText";
import { ThemedTextInput } from "@/components/ThemedTextInput";
import { ThemedView } from "@/components/ThemedView";
import { useSession } from "@/store/auth/auth-context";
import { useColorScheme } from "@/hooks/useColorScheme";
import { AuthInputValueKey, AuthFormProps } from "@/types/auth-types";
import { handleValidateInputItem } from "@/utilities/validation-utilities";
Тепер ми знову використовуємо наші хуки для тем, і деякі правила inline CSS були перенесені в об'єкт стилів.
import { Colors } from "@/constants/Colors";
export default function AuthForm() {
const { signIn, signUp } = useSession();
const colorScheme = useColorScheme();
const windowHeight = useWindowDimensions().height;
const width = useWindowDimensions().width;
const [errors, setErrors] = useState({
email: "",
displayName: "",
password: "",
confirmPassword: "",
});
const [inputValue, setInputValue] = useState({
email: "",
displayName: "",
password: "",
confirmPassword: "",
});
const [canSubmit, setCanSubmit] = useState(false);
const [mode, setMode] = useState("signIn");
const [securePasswordEntry, setSecurePasswordEntry] = useState(true);
const isSignUp = mode === "signUp";
const styles = ScaledSheet.create({
"centered-text": {
textAlign: "center",
},
"form-container": {
paddingBottom: "20@ms",
},
"input-container": {
display: "flex",
import { Colors } from "@/constants/Colors";
export default function AuthForm() {
const { signIn, signUp } = useSession();
const colorScheme = useColorScheme();
const windowHeight = useWindowDimensions().height;
const width = useWindowDimensions().width;
const [errors, setErrors] = useState({
email: "",
displayName: "",
password: "",
confirmPassword: "",
});
const [inputValue, setInputValue] = useState({
email: "",
displayName: "",
password: "",
confirmPassword: "",
});
const [canSubmit, setCanSubmit] = useState(false);
const [mode, setMode] = useState("signIn");
const [securePasswordEntry, setSecurePasswordEntry] = useState(true);
const isSignUp = mode === "signUp";
const styles = ScaledSheet.create({
"centered-text": {
textAlign: "center",
},
"form-container": {
paddingBottom: "20@ms",
},
"input-container": {
display: "flex",
Тут ми знову використовуємо хук для аутентифікації (useSession), а також визначаємо різні стани для полів форми, перевірки помилок, режиму (реєстрація чи вхід), а також для безпеки пароля. Стилі для компонентів також визначаються через ScaledSheet з бібліотеки react-native-size-matters.
flex: 1,
flexDirection: "column",
justifyContent: "center",
gap: "15@ms",
width: "90%",
},
wrapper: {
alignItems: "center",
display: "flex",
flex: 1,
flexDirection: "column",
justifyContent: "center",
margin: 0,
padding: 0,
minHeight: Math.max(windowHeight),
width: Math.max(width),
},
});
const placeholders: Record = {
email: "Email Address",
displayName: isSignUp ? "Display Name" : "",
password: "Password",
confirmPassword: isSignUp ? "Confirm Password" : "",
};
useEffect(() => {
const hasErrors = Object.values(errors).some((error) => error !== "");
const allFieldsFilled = Object.keys(inputValue)
.filter((field) => placeholders[field as AuthInputValueKey])
.every((key) => inputValue[key as AuthInputValueKey] !== "");
setCanSubmit(!hasErrors && allFieldsFilled);
}, [errors, inputValue]);
const handleSubmit = () => {
let isValid = true;
flex: 1,
flexDirection: "column",
justifyContent: "center",
gap: "15@ms",
width: "90%",
},
wrapper: {
alignItems: "center",
display: "flex",
flex: 1,
flexDirection: "column",
justifyContent: "center",
margin: 0,
padding: 0,
minHeight: Math.max(windowHeight),
width: Math.max(width),
},
});
const placeholders: Record = {
email: "Email Address",
displayName: isSignUp ? "Display Name" : "",
password: "Password",
confirmPassword: isSignUp ? "Confirm Password" : "",
};
useEffect(() => {
const hasErrors = Object.values(errors).some((error) => error !== "");
const allFieldsFilled = Object.keys(inputValue)
.filter((field) => placeholders[field as AuthInputValueKey])
.every((key) => inputValue[key as AuthInputValueKey] !== "");
setCanSubmit(!hasErrors && allFieldsFilled);
}, [errors, inputValue]);
const handleSubmit = () => {
let isValid = true;
У цьому коді ми визначаємо стилі для компонента форми та перевіряємо, чи всі поля заповнені, і чи немає помилок. Використовуємо хук useEffect для перевірки, чи можна надіслати форму (кнопка submit стане доступною, якщо немає помилок і всі поля заповнені). Стилі зберігаються в ScaledSheet для зручності використання на різних розмірах екранів.
It looks like you're working on a form validation process in TypeScript, where you're handling input validation and submitting data based on the validity of the fields. You're using `Object.keys` to loop through the `inputValue` object and check each field for validity. Here’s a breakdown of what’s happening in the code:
1. **Input Validation:**
You're using `handleValidateInputItem` to validate each input item. If the field is invalid, you're setting the error for that field using `setErrors`.
2. **Error Handling:**
When an error is found, you're updating the error state by appending a message like "Please enter a valid [placeholder]" to `setErrors`.
3. **Form Submission:**
After validation, if the form is valid (`isValid` is true), you're calling either the `signUp` or `signIn` function based on the `isSignUp` flag, and then redirecting to the home page using `router.push("/")`.
4. **Rendering Fields:**
In the `return` statement, you're rendering form fields by mapping over the keys of `inputValue`. You're checking if a placeholder exists for each field before rendering it.
If you’re looking for suggestions or clarifications, here are a few things you might consider:
### Potential Improvements:
- **Error Messages:** Instead of hardcoding the error message like `"Please enter a valid [placeholder]"`, you could create a helper function that dynamically constructs error messages to handle different cases more elegantly.
- **Type Safety:** You’re using `key as AuthInputValueKey` multiple times. If `inputValue` and `placeholders` are strongly typed, ensure that `AuthInputValueKey` is correctly typed to avoid potential issues with key mismatches.
- **Async Validation:** If `handleValidateInputItem` involves async logic (e.g., checking an email or username against a server), you might need to handle async behavior with `async/await` or promises.
Would you like help refining or debugging this code further?
This is a part of your form's JSX, where you are rendering the input fields for your authentication form. Here's a breakdown of the logic and suggestions to improve readability or functionality:
### Key Parts:
1. **Dynamic Placeholder and Value Binding:**
- You're setting the `placeholder` dynamically using the `placeholders[field as AuthInputValueKey]`.
- The value of the input field is dynamically bound to `inputValue[field]`.
2. **Handling Change Events:**
- You’re using the `onChange` handler to update the `inputValue` state. You’re spreading the previous state (`...inputValue`) and updating the specific field using `e.nativeEvent.text`.
3. **Blur Event (Validation):**
- The `onBlur` event triggers `handleValidateInputItem` for each field, performing validation when the user leaves the input.
4. **Focus Event (Clearing Errors):**
- The `onFocus` event clears any existing errors for the specific field by resetting `setErrors`.
5. **Validation Error Handling:**
- You're passing a `validationError` prop to the input field, which is `true` if there’s an error for that field (determined by checking `errors[field]`).
- You also pass `validationErrorMessage` to show the specific error message for that field.
6. **Secure Text Entry (Password Fields):**
- If the field is either `password` or `confirmPassword`, you set `secureTextEntry` to the value of `securePasswordEntry`, which toggles visibility of the password.
- The `icon` for password visibility is conditionally rendered, and it toggles the `securePasswordEntry` state when clicked.
### Possible Enhancements:
1. **Password Visibility Toggle:**
- You're conditionally rendering an icon for the password field and toggling the visibility of the password on click. It seems like the icon code is incomplete. Make sure the icon rendering part looks like this:
tsx
icon={field === "password" ? (
<Icon
name={securePasswordEntry ? "eye-off" : "eye"}
onPress={() => setSecurePasswordEntry(!securePasswordEntry)}
size={ms(24)}
color={Colors[colorScheme ?? "light"].icon ?? Colors[colorScheme ?? "dark"].icon}
/>
) : null}
2. **Type Safety for Field Keys:**
- Ensure `field as AuthInputValueKey` is type-safe, meaning that `field` should always match a key in `AuthInputValueKey`. If you’re not sure about the type of `field`, consider narrowing the type before using it.
3. **Debouncing Validation:**
- If validation is expensive or if you want to prevent unnecessary calls to `handleValidateInputItem` during typing, consider debouncing the `onBlur` or `onChange` events.
4. **Error Message Display:**
- If the `validationErrorMessage` is critical, consider making the error display more prominent, perhaps with a red color or an icon next to the input.
### Revised Example (with Missing Icon Fix):
tsx
<CustomInput
placeholder={placeholders[field as AuthInputValueKey]}
value={inputValue[field as AuthInputValueKey]}
onChange={(e) =>
setInputValue({
...inputValue,
[field]: e.nativeEvent.text,
})
}
onBlur={() =>
handleValidateInputItem(inputValue, field as AuthInputValueKey, setErrors)
}
onFocus={() => {
setErrors((prevErrors) => ({
...prevErrors,
[field]: "",
}));
}}
validationError={!!errors[field as AuthInputValueKey]}
validationErrorMessage={errors[field as AuthInputValueKey]}
secureTextEntry={field === "password" || field === "confirmPassword" ? securePasswordEntry : false}
icon={field === "password" ? (
<Icon
name={securePasswordEntry ? "eye-off" : "eye"}
onPress={() => setSecurePasswordEntry(!securePasswordEntry)}
size={ms(24)}
color={Colors[colorScheme ?? "light"].icon ?? Colors[colorScheme ?? "dark"].icon}
/>
) : null}
/>
This is a cleaner version of your component. Would you like any further adjustments or clarifications on this?
onPress={() =>
setSecurePasswordEntry(!securePasswordEntry)
}
size={ms(24)}
color={
Colors[colorScheme ?? "light"].icon ??
Colors[colorScheme ?? "dark"].icon
}
/>
)
) : undefined
}
/>
);
})}
{isSignUp ? "Зареєструватися" : "Увійти"}
{
setMode(isSignUp ? "signIn" : "signUp");
setErrors({
email: "",
displayName: "",
password: "",
confirmPassword: "",
});
setInputValue({
email: "",
displayName: "",
password: "",
confirmPassword: "",
});
}}
style={styles["centered-text"]}
type="link"
>
{isSignUp
? "Уже маєте акаунт? Увійти"
: "Не маєте акаунту? Зареєструватися"}
);
}
У Частині 4 ми завершили потік аутентифікації, додавши кнопку виходу, яка ефективно завершує сесію користувача та очищає локальне сховище від даних користувача.
Ми також витратили час на вдосконалення деяких стилів CSS, забезпечивши безшовну інтеграцію з нашою динамічною кольоровою схемою.
Залишайтеся з нами в Частині 5, де ми піднімемо процес аутентифікації на новий рівень, інтегрувавши Firebase Authentication для безпечного та масштабованого управління користувачами!
Перекладено з: Expo React Native Tutorials