首页 > 解决方案 > 未捕获的类型错误:无法读取未定义的属性“displayName”

问题描述

所以我的项目是一个 reactjs web 应用程序,它将其数据存储在 firestore 实时数据库中。最初所有组件都是基于类的,但我将其全部重构为功能组件,那时我遇到了标题中提到的错误。

Uncaught TypeError: Cannot read property 'displayName' of undefined
    at Chat (Chat.js:61)
    at renderWithHooks (react-dom.development.js:14985)
    at updateFunctionComponent (react-dom.development.js:17356)
    at beginWork (react-dom.development.js:19063)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)
    at beginWork$1 (react-dom.development.js:23964)
    at performUnitOfWork (react-dom.development.js:22776)
    at workLoopSync (react-dom.development.js:22707)
    at renderRootSync (react-dom.development.js:22670)
    at performSyncWorkOnRoot (react-dom.development.js:22293)
    at react-dom.development.js:11327
    at unstable_runWithPriority (scheduler.development.js:468)
    at runWithPriority$1 (react-dom.development.js:11276)
    at flushSyncCallbackQueueImpl (react-dom.development.js:11322)
    at flushSyncCallbackQueue (react-dom.development.js:11309)
    at flushPassiveEffectsImpl (react-dom.development.js:23620)
    at unstable_runWithPriority (scheduler.development.js:468)
    at runWithPriority$1 (react-dom.development.js:11276)
    at flushPassiveEffects (react-dom.development.js:23447)
    at react-dom.development.js:23324
    at workLoop (scheduler.development.js:417)
    at flushWork (scheduler.development.js:390)
    at MessagePort.performWorkUntilDeadline (scheduler.development.js:157)

该错误是针对我的代码中的这一行的:

Header />
  59 | </div>
  60 | <div className="user-info">
> 61 |  Logged in as: <strong id="user-tag">{state.user.displayName}</strong>
     | ^  62 | </div>
  63 | {console.log("the log: " + state.user.displayName)}
  64 | <div className="chatbox">

我也收到此错误:

    index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
        at Chat (http://localhost:3000/static/js/main.chunk.js:804:83)
        at Route (http://localhost:3000/static/js/vendors~main.chunk.js:114703:29)
        at PrivateRoute (http://localhost:3000/static/js/main.chunk.js:38:14)
        at Switch (http://localhost:3000/static/js/vendors~main.chunk.js:114905:29)
        at Router (http://localhost:3000/static/js/vendors~main.chunk.js:114338:30)
        at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:113958:35)
        at App (http://localhost:3000/static/js/main.chunk.js:99:83)
    console.<computed> @ index.js:1
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({ readError: null });
        console.log("read error set to null now attempting to fetch data");
        try {
            db.ref("messages").on("value", (snapshot) => {
                let chats = [];
                snapshot.forEach((snap) => {
                    chats.push(snap.val());
                });
                setState({ chats });
            });
        } catch (err) {
            this.setState({ readError: err.message });
        }
        console.log("data fetched");
    }, []);

    function handleChange(event) {
        setState({
            content: event.target.value,
        });
    }

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

    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>
            {console.log("the log: " + state.user.displayName)}
            <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.error ? <p>{state.writeError}</p> : null}
                        <button className="send-btn" type="submit">
                            Send
                        </button>
                    </form>
                </div>
            </div>
        </div>
    );
}

export default Chat;

这可能是一些基本错误,但我不知道该怎么做,请多多包涵。我的一个猜测是,这行可能是state.user.displayName指我的状态的旧版本,我用useState()钩子创建它,因为我读到状态中的每个更新都会重新渲染整个组件,所以也许我需要使用上下文,但我不确定,所以请帮助我,如果您需要任何详细信息,请告诉我。

编辑说明:我已经发布了这个错误的完整代码,请让我知道我在这里犯了什么其他错误,比如我违反了任何编码实践或任何其他严重错误。啊,我知道那些内联 css 语句很糟糕,对此感到抱歉。

标签: reactjsfirebasefirebase-authenticationreact-hooks

解决方案


请注意,基于类的行为setState()不同于功能挂钩的替代方案 ( useState)。前者将您提供的对象与当前状态合并。在 hooks 的情况下 - 对象被完全替换,而不保留当前状态的任何内容,因此您只剩下提供给setXsetter 的内容。

这正是您的问题的原因。

您没有正确修改状态。您正在替换整个state对象而不保留未修改的值。例如:

useEffect(() => {
    setState({ readError: null });
    console.log("read error set to null now attempting to fetch data");
    try {
        db.ref("messages").on("value", (snapshot) => {
            let chats = [];
            snapshot.forEach((snap) => {
                chats.push(snap.val());
            });
            setState({ chats });
        });
    } catch (err) {
        this.setState({ readError: err.message });
    }
    console.log("data fetched");
}, []);

更具体地说:

setState({ chats });

在这里,你正在更换你的

{
    user: auth().currentUser,
    chats: [],
    content: "",
    readError: null,
    writeError: null,
}

对象:

{
    chats: [...]
}

因此你不再有state.user错误。你应该这样做:

setState(state => ({ ...state, chats: chats }));

您应该为所有状态更改执行此操作(我猜)。

此外,例如,您还有尚未清除的与类相关的工件this.setState({ readError: err.message });this在功能组件中使用是禁忌。

从这里开始并在必要时进行修改:

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(() => {
        try {
            db.ref("messages").on("value", (snapshot) => {
                let chats = [];

                snapshot.forEach((snap) => {
                    chats.push(snap.val());
                });

                setState(state => {
                    return {
                        ...state,
                        chats: chats
                    };
                });
            });
        }
        catch (err) {
            setState(state => {
                return {
                    ...state,
                    readError: err.message
                };
            });
        }

        console.log("data fetched");
    }, []);

    function handleChange(event) {
        setState(state => {
            return {
                ...state,
                content: event.target.value
            };
        });
    }

    async function handleSubmit(event) {
        event.preventDefault();

        setState(state => {
            return {
                ...state,
                writeError: null
            };
        });

        try {
            await writeChats({
                name: state.user.displayName,
                content: state.content,
                timestamp: Date.now(),
                uid: state.user.uid,
            });

            setState(state => {
                return {
                    ...state,
                    content: ""
                };
            });
        }
        catch (error) {
            setState(state => {
                return {
                    ...state,
                    writeError: error.message
                };
            });
        }
    }

    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>
            {console.log("the log: " + state.user.displayName)}
            <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.error ? <p>{state.writeError}</p> : null}
                        <button className="send-btn" type="submit">
                            Send
                        </button>
                    </form>
                </div>
            </div>
        </div>
    );
}

export default Chat;

另外,我建议您将您的state变量分成不同的变量(不同useState的调用),因为其中一些看起来不相关,只会损害可维护性和清晰度。


推荐阅读