Next.js组件报TypeError: Cannot read properties of null (reading 'useState')
问题分析与修复方案
错误根源
- 客户端组件不能是异步函数:你的
TodoList组件标记了"use client",属于客户端组件,但你将其定义为async function——React客户端组件不允许是异步函数(异步组件是Next.js服务器组件的特性),这会导致React无法正确初始化组件上下文,进而抛出useState读取null的错误。 - 渲染期间直接调用状态更新:组件顶层的
if (todos) { setDisplayedTodos(todos); }是在渲染流程中直接调用setState,违反React渲染规则,会触发不必要的重渲染,甚至引发循环问题。
修复步骤
1. 移除组件的async标识,改用useEffect获取数据
客户端组件获取远程数据需通过useEffect钩子(或SWR、React Query等数据库),不能直接在组件顶层使用await。
2. 修正状态更新逻辑
将数据获取与状态更新移至useEffect中,避免在渲染阶段调用setState。
修复后的完整代码
"use client"; import fetchTodos from "@/lib/fetchTodos"; import { useState, useEffect } from "react"; import TodoItem from "./TodoItem"; import Filters from "./Filters"; export default function TodoList() { const [todos, setTodos] = useState<Todo[] | undefined>(undefined); const [displayedTodos, setDisplayedTodos] = useState<Todo[] | undefined>(undefined); // 组件挂载后获取数据 useEffect(() => { const loadTodos = async () => { const fetchedData = await fetchTodos(); setTodos(fetchedData); setDisplayedTodos(fetchedData); }; loadTodos(); }, []); const filterTodos = (filteredTodos: Todo[]) => { setDisplayedTodos(filteredTodos); }; let content; if (!todos || todos.length === 0 || displayedTodos === undefined) { content = <p>No todos available</p>; } else { // reverse会修改原数组,先拷贝再反转 const sortedTodos = [...displayedTodos].reverse(); content = ( <ul> {sortedTodos.map((todo) => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> ); } return ( <> <Filters todos={todos} filterFunction={filterTodos} /> {content} </> ); }
额外优化点
- 给列表项添加
key属性:React列表渲染要求每个子项有唯一key,避免渲染警告与性能问题。 - 避免直接修改状态数组:
reverse()会直接修改原数组,通过扩展运算符[...displayedTodos]拷贝后再处理,防止意外污染状态数据。
内容的提问来源于stack exchange,提问作者Jehonatán Chávez
相关产品推荐
相关产品推荐

