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

Next.js自定义useTasks Hook值变更不触发重渲染问题求助

问题分析与解决方案

核心问题

你遇到的page.tsx不重渲染的根本原因是:Table组件独立调用了useTasks(),导致它和page.tsx中的useTasks是两个完全隔离的状态实例。也就是说,Table里调用setTaskCompleted只会更新自己组件内的allOpenTasks,而page.tsx中的allOpenTasks完全没有变化,自然不会触发重渲染。你猜测的初始化时allOpenTasks为空只是表象,本质是状态实例不共享。

修复方案

1. 共享Hook实例:通过props传递更新函数

让Table组件不再单独调用useTasks,而是从page.tsx接收setTaskCompleted和任务数据,确保两者共享同一个状态实例。

修改page.tsx

"use client"
import Table from './Table';

const { allOpenTasks, getOpenTasks, setTaskCompleted } = useTasks();

useEffect(() => {
  const fetchTasks = async () => {
    await getOpenTasks(user.id);
  };
  fetchTasks();
}, [user.id, getOpenTasks]); // 补充依赖,避免lint警告

return <Table tasks={allOpenTasks} setTaskCompleted={setTaskCompleted} />;

修改Table.tsx

"use client"
import { supabase } from '../path-to-your-supabase-config';

// 定义props类型,避免any
interface TableProps {
  tasks: Array<{ id: string; is_complete: boolean }>;
  setTaskCompleted: (taskId: string) => void;
}

export default function Table({ tasks, setTaskCompleted }: TableProps) {
  const clickCompleteTask = async (taskId: string) => {
    try {
      const { error } = await supabase
        .from("task")
        .update({ is_complete: true })
        .eq("id", taskId);

      if (error) throw error;
      setTaskCompleted(taskId);
    } catch (err) {
      console.error('更新任务状态失败:', err);
    }
  };

  // 渲染表格逻辑
  return (
    <table>
      {tasks.map(task => (
        <tr key={task.id} onClick={() => clickCompleteTask(task.id)}>
          <td>{task.id}</td>
          <td>{task.is_complete ? '已完成' : '未完成'}</td>
        </tr>
      ))}
    </table>
  );
}

2. 优化useTasks中的状态更新:使用函数式更新

为了避免闭包捕获旧的allOpenTasks值,修改setTaskCompleted为函数式更新,确保每次修改都基于最新的状态:

修改useTasks.tsx

"use client"
import { useState } from 'react';
import { supabase } from '../path-to-your-supabase-config';

// 定义Task类型,替换any
interface Task {
  id: string;
  is_complete: boolean;
}

export const useTasks = () => {
  const [allOpenTasks, setAllOpenTasks] = useState<Task[]>([]);

  const getOpenTasks = async (userId: string) => {
    try {
      const { data, error } = await supabase
        .from("task")
        .select("id, is_complete")
        .eq("owner_id", userId);

      if (error) throw error;
      if (data) setAllOpenTasks(data as Task[]);
    } catch (error: any) {
      console.error('获取任务失败:', error.message);
    }
  };

  // 改用函数式更新,确保基于最新状态修改
  const setTaskCompleted = (taskId: string) => {
    setAllOpenTasks(prevTasks => 
      prevTasks.map(task => 
        task.id === taskId ? { ...task, is_complete: !task.is_complete } : task
      )
    );
  };

  return {
    allOpenTasks,
    getOpenTasks,
    setTaskCompleted
  };
};

更优实现模式

  1. 共享状态规范:对于需要跨组件共享的状态,统一在父层级的Hook中管理,子组件通过props接收状态和更新函数,避免多个Hook实例导致状态不一致。
  2. 函数式状态更新:当状态更新依赖于之前的状态时,始终使用setState(prevState => newState)的形式,规避闭包陷阱。
  3. 类型安全:抛弃any,定义明确的TypeScript接口,提升代码可维护性和可读性。
  4. 数据一致性保障:修改后端数据后,优先用函数式更新本地状态(高效),如果对数据一致性要求极高,也可以在更新成功后重新调用getOpenTasks拉取最新数据(适合复杂场景)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 10:46:18