You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React类组件改函数组件后报displayName未定义错误如何解决

问题原因及修复方案

1. 首要报错:Cannot read property 'displayName' of undefined

这个错误的核心原因是函数组件的setState(useState返回的更新函数)是全量替换状态,而不是类组件那样的增量合并:
你初始化state的时候包含了user、chats等多个字段,但后续调用setState({readError: null})、setState({chats})的时候,直接用新对象覆盖了整个state,导致原来的user字段直接丢失,所以state.user变成了undefined,访问displayName自然报错。

2. 内存泄漏警告:Can't perform a React state update on an unmounted component

原因是你给Firebase实时数据库绑定了value事件监听,但组件卸载时没有取消监听,当组件销毁后监听回调触发,还在尝试给已卸载的组件更新状态就会触发这个警告。

3. 其他隐藏问题

  • useEffect的catch块里还在用类组件的this.setState,函数组件没有this,这个写法会直接报错。
  • 模板里判断错误的字段写错了:你存的错误字段是writeError,但模板里判断的是state.error,导致发送消息的错误永远不会显示。
  • 没有处理auth().currentUser初始为null的情况:Firebase Auth初始化是异步的,组件首次渲染时currentUser可能还没加载完成,本身就可能是null。

完整修复代码

import React, { useState, useEffect } from "react";
import { auth } from "../services/firebase";
import { db } from "../services/firebase";
import Header from "../components/Header";
import { writeChats } from "../helpers/db";

function Chat() {
    const [state, setState] = useState({
        user: auth().currentUser,
        chats: [],
        content: "",
        readError: null,
        writeError: null,
    });

    useEffect(() => {
        // 所有setState都要先合并旧状态,再更新指定字段
        setState(prev => ({...prev, readError: null }));
        let unsubcribe = null;
        try {
            // 保存监听取消函数
            unsubcribe = db.ref("messages").on("value", (snapshot) => {
                let chats = [];
                snapshot.forEach((snap) => {
                    chats.push(snap.val());
                });
                setState(prev => ({...prev, chats }));
            });
        } catch (err) {
            // 去掉this,同时合并状态
            setState(prev => ({...prev, readError: err.message }));
        }
        // 返回清理函数,组件卸载时取消监听
        return () => {
            if (unsubcribe) {
                db.ref("messages").off("value", unsubcribe);
            }
        };
    }, []);

    function handleChange(event) {
        // 合并旧状态
        setState(prev => ({
            ...prev,
            content: event.target.value,
        }));
    }

    async function handleSubmit(event) {
        event.preventDefault();
        setState(prev => ({...prev, writeError: null }));
        try {
            await writeChats({
                name: state.user.displayName,
                content: state.content,
                timestamp: Date.now(),
                uid: state.user.uid,
            });
            setState(prev => ({...prev, content: "" }));
        } catch (error) {
            setState(prev => ({...prev, writeError: error.message }));
        }
    }

    // 加user为空的兜底判断
    if (!state.user) {
        return <div>登录信息加载中...</div>
    }

    return (
        <div className="chatter">
            <div className="header">
                <Header />
            </div>
            <div className="user-info">
                Logged in as: <strong id="user-tag">{state.user.displayName}</strong>
            </div>
            <div className="chatbox">
                <div className="chats">
                    {state.chats.map((chat) => {
                        return (
                            <p key={chat.timestamp} style={{ fontSize: "1.2rem" }}>
                                {chat.content}
                                <code style={{ fontSize: "0.5rem" }}>&nbsp;{chat.name}</code>
                            </p>
                        );
                    })}
                </div>
                <div className="input-group">
                    <form className="textbar" onSubmit={handleSubmit}>
                        <input
                            style={{ borderRadius: "10px" }}
                            size="50"
                            onChange={handleChange}
                            value={state.content}
                        ></input>
                        {/* 修正错误字段判断 */}
                        {state.writeError ? <p>{state.writeError}</p> : null}
                        <button className="send-btn" type="submit">
                            Send
                        </button>
                    </form>
                </div>
            </div>
        </div>
    );
}

export default Chat;

额外优化建议

如果你的项目多个组件都需要用到用户登录信息,确实可以考虑用Context把用户状态抽离到上层,避免每个组件都单独获取auth().currentUser,也能更好处理auth异步初始化的逻辑。

内容的提问来源于stack exchange,提问作者Anand Thevar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.14 17:36:11