Провайдер контекста React вызывает сбой приложения при изменении ввода в форме

Вопрос или проблема

Я предоставляю состояние аутентификации Firebase Auth в провайдере контекста React и использую его в форме аутентификации (чтобы показывать/скрывать компоненты в зависимости от состояния аутентификации). В данный момент только поле электронной почты использует хук useState. Когда я ввожу данные в поле электронной почты, вся страница зависает. Мне нужно несколько кликов, чтобы закрыть вкладку в Chrome. Когда я удаляю провайдер контекста из формы, хук useState работает нормально.

Что может вызывать сбой?

import React, { useState, createContext, useContext } from 'react';
import { auth, onAuthStateChanged } from 'utilities/firebase.client.js';

const UserContext = createContext(auth.currentUser);
function UserContextProvider({ children }){
    var [user, setUser] = useState(auth.currentUser);

    onAuthStateChanged(auth, function(u){
        if(u){
            setUser(u);
        } else{
            setUser(null);
        }
    });

    return (
        <UserContext.Provider value={user}>
            { children }
        </UserContext.Provider>
    );
};

function Anonymous({ children }){
    var u = useContext(UserContext);

    if(u){
        return null;
    } else{
        return (
            <>
                { children }
            </>
        );
    }
}

function Identified({ children }){
    var u = useContext(UserContext);

    if(u){
        return (
            <>
                { children }
            </>
        );
    } else{
        return null;
    }
}

function AuthForm(){
    var [email, setEmail] = useState();
    return (
        <UserContextProvider>
            <Anonymous>
                <form className="auth_form">
                    <input type="email" name="email" placeholder="Email address" onChange={(v) => { v = v.target.value; setEmail(v); }}/>
                    <input type="password" name="password" placeholder="Password"/>
                    <div>{email}</div>
                    <CreateAccountButton email="" password="">Создать аккаунт с электронной почтой</CreateAccountButton>
                    <PasswordLoginButton email="" password=""/>
                    <GoogleLoginButton/>
                    <FacebookLoginButton/>
                    <TwitterLoginButton/>
                    <MicrosoftLoginButton/>
                </form>
            </Anonymous>
            <Identified>
                <LogoutButton>Выйти</LogoutButton>
                <DeleteUserButton>Удалить аккаунт</DeleteUserButton>
            </Identified>
        </UserContextProvider>
    );
}

Ответ или решение

Проблема, с которой вы столкнулись, связана с тем, как глаза кода обрабатывают изменения состояния в контексте React. Давайте подробно разберем, что именно может вызывать зависание приложения при изменении состояния просмотра в компоненте формы.

Контекстная Проблема Состояния

Вы используете onAuthStateChanged для наблюдения за изменениями статуса аутентификации пользователя, и это может быть причиной мерцания или повторного рендеринга, который вызван изменениями состояния. Ваша реализация позволяет подписаться на изменения аутентификации, но вы не учитываете, что подписка выполняется при каждом рендере компонента UserContextProvider.

Проблема Каждый Рендер

Каждый раз, когда компонент UserContextProvider ререндерится (в результате вызова setEmail), вызывается снова функция onAuthStateChanged, что приводит к множественным подпискам. Это и вызывает бесконечный цикл обновления, из-за которого страдает производительность, пока приложение не зависнет.

Решение

Чтобы устранить это поведение, необходимо поместить вызов onAuthStateChanged внутри useEffect, который сработает только один раз при монтировании компонента. Использование useEffect предотвратит повторное создание подписки на каждое изменение состояния. Внесите следующие изменения:

import React, { useState, createContext, useContext, useEffect } from 'react';
import { auth, onAuthStateChanged } from 'utilities/firebase.client.js';

const UserContext = createContext(null);

function UserContextProvider({ children }) {
    const [user, setUser] = useState(auth.currentUser);

    useEffect(() => {
        const unsubscribe = onAuthStateChanged(auth, (u) => {
            setUser(u);
        });
        // Очистка подписки при размонтировании компонента
        return () => unsubscribe();
    }, []);

    return (
        <UserContext.Provider value={user}>
            {children}
        </UserContext.Provider>
    );
}

function Anonymous({ children }) {
    const user = useContext(UserContext);

    if (user) {
        return null;
    }
    return <>{children}</>;
}

function Identified({ children }) {
    const user = useContext(UserContext);

    if (user) {
        return <>{children}</>;
    }
    return null;
}

function AuthForm() {
    const [email, setEmail] = useState('');

    return (
        <UserContextProvider>
            <Anonymous>
                <form className="auth_form">
                    <input type="email" name="email" placeholder="Email address" 
                           onChange={(e) => setEmail(e.target.value)} />
                    <input type="password" name="password" placeholder="Password"/>
                    <div>{email}</div>
                    <CreateAccountButton email="" password="">Create Account with Email</CreateAccountButton>
                    <PasswordLoginButton email="" password=""/>
                    <GoogleLoginButton/>
                    <FacebookLoginButton/>
                    <TwitterLoginButton/>
                    <MicrosoftLoginButton/>
                </form>
            </Anonymous>
            <Identified>
                <LogoutButton>Logout</LogoutButton>
                <DeleteUserButton>Delete Account</DeleteUserButton>
            </Identified>
        </UserContextProvider>
    );
}

Ключевые Изменения

  1. Использование useEffect: Это предотвратит повторное создание слушателей на каждый рендер компонента.
  2. Очистка подписки: Важно очищать слушателя аутентификации при размонтировании компонента, чтобы избежать утечек памяти.

Заключение

Ваша проблема зависания приложения на входе в поле может быть решена перемещением подписки на onAuthStateChanged в useEffect, что значительно улучшит производительность и стабильность вашего приложения. Такие паттерны достаточно часто встречаются в React, и их следует внимательно применять, чтобы избежать неожиданных последствий.

Оцените материал
Добавить комментарий

Капча загружается...