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

NextJS中user.displayName读取报错:Cannot read properties of null

解决NextJS+Firebase中user.displayName读取null属性的错误

问题场景

我在NextJS结合Firebase开发时,已在_app.js中通过useState监听auth.onAuthStateChanged状态,将user props传递给所有组件。在[postid].jsx组件中,执行console.log(user)能输出包含displayName属性的用户对象,但执行console.log(user.displayName)时却抛出错误:TypeError: Cannot read properties of null (reading 'displayName')。该问题仅出现在[postid].jsx文件中,且console.log(user)会同时输出null和用户对象。

相关代码

[postid].jsx

const post = ({ post, user }) => {
  const [name, setName] = useState("");
  const [addComment, setAddComment] = useState("");

  const router = useRouter();
  const { postid } = router.query;

  console.log(user); // 会输出null和用户对象
  console.log(user.displayName); // 抛出错误

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!addComment || !name) {
      alert("Please fill all the fields proplerly!");
    } else {
      db.collection("posts").doc(postid).collection("comments").add({
        name: name,
        comment: addComment,
      });
    }
  };
  return (
    <div className="post container text-center">
      <h2>{post.title}</h2>
      <h5>Created On - {new Date(post.createdAt).toDateString()}</h5>
      <br />
      <img src={post.imageUrl} alt={post.alt} />
      <br />
      <p>{post.description}</p>
      <br />
      <form>
        <div className="form-input">
          <input
            type="text"
            name="name"
            id="name"
            className="name"
            value={name}
            onChange={(e) => setName(e.target.value)}
            placeholder="Enter Your Name"
          />
        </div>
        <div className="form-input">
          <textarea
            name="add-comment"
            id="add-comment"
            className="add-comment"
            value={addComment}
            onChange={(e) => setAddComment(e.target.value)}
            cols="100"
            rows="3"
            placeholder="Add your Comment"
          ></textarea>
        </div>
        <button onClick={handleSubmit} className="btn-warning">
          Submit
        </button>
      </form>

      <div className="comments">
        <p className="comment">
          <b>
            <i>Name</i>:
          </b>{" "}
          <span>Comment</span>
        </p>
      </div>
    </div>
  );
};

export default post;

export async function getServerSideProps({ params }) {
  const result = await db.collection("posts").doc(params.postid).get();
  return {
    props: {
      post: {
        ...result.data(),
        createdAt: result.data().createdAt.toMillis(),
      },
    },
  };
}

_app.js

export default function App({ Component, pageProps }) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    auth.onAuthStateChanged((user) => {
      if (user) {
        setUser(user);
      } else {
        setUser(null);
      }
    });
  }, []);
  return (
    <div className="app">
      <div className="banner-div">
        <Navbar user={user} />
        <img src="/banner.jpg" alt="School Banner" className="banner" />
      </div>
      <Component {...pageProps} user={user} />
    </div>
  );
}

问题原因

核心是异步渲染的时序问题:

  • _app.js中user的初始状态是null,所有子组件会先以这个初始值完成第一次渲染。
  • auth.onAuthStateChanged是异步方法,Firebase需要时间完成用户身份验证状态的拉取,回调函数触发后才会更新user为真实用户对象。
  • 所以[postid].jsx会执行两次渲染:第一次user为null,第二次才是完整的用户对象。直接访问user.displayName时,第一次渲染就会因为user是null而抛出错误,而console.log(user)会打印两次结果。

解决方案

方案1:使用可选链操作符(推荐)

用?.语法在访问属性前自动判断user是否为null/undefined,避免报错:

console.log(user?.displayName);
// 也可以配合默认值使用
const userName = user?.displayName || "匿名用户";

方案2:添加条件判断

在访问user属性前先做非空检查,或者在组件渲染时添加加载状态:

// 仅在user存在时打印属性
if (user) {
  console.log(user.displayName);
}

// 组件渲染时先显示加载状态
if (!user) {
  return <div>加载中...</div>;
}
return (
  // 原组件内容
);

方案3:优化_app.js初始状态

先尝试获取auth.currentUser(如果用户之前登录过,这个值可能在初始化时就存在),减少null状态的渲染次数:

export default function App({ Component, pageProps }) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    // 先检查当前已登录的用户
    const currentUser = auth.currentUser;
    if (currentUser) {
      setUser(currentUser);
    }
    // 监听状态变化并在组件卸载时取消监听
    const unsubscribe = auth.onAuthStateChanged((user) => {
      setUser(user || null);
    });
    return () => unsubscribe();
  }, []);
  return (
    <div className="app">
      <div className="banner-div">
        <Navbar user={user} />
        <img src="/banner.jpg" alt="School Banner" className="banner" />
      </div>
      <Component {...pageProps} user={user} />
    </div>
  );
}

内容的提问来源于stack exchange,提问作者Agrim Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 22:13:07