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

Remix中react-hot-toast重复触发8次的无障碍通知问题求助

盲态开发者Remix Todo应用无障碍通知重复播报问题排查

我是一名盲态Web开发者,正在用Remix构建简易Todo应用。功能正常,但成功添加Todo时的无障碍通知会重复播报8次。我用了role="status"属性,预期仅在内容变更时触发播报,但在NVDA+Chrome环境下,相同消息重复播报8次,求排查思路。

相关代码

首页组件(index route)

import toast, { Toaster, resolveValue } from "react-hot-toast";
import { json, redirect } from "@remix-run/node";
import type {
  ActionArgs,
  ActionFunction,
  LoaderFunction,
} from "@remix-run/node";
import {
  Form,
  useActionData,
  useLoaderData,
  useNavigation,
} from "@remix-run/react";
import { db } from "~/utils/db";
import type { todo } from "~/types/todo";

export const loader: LoaderFunction = async () => {
  const data = await db.todo.findMany();
  return json(data);
};

export const action: ActionFunction = async ({ request }: ActionArgs) => {
  const data = await request.formData();
  const name = data.get("name");
  const complete = data.get("complete") === null ? false : true;
  const description = data.get("description");
  if (
    typeof description !== "string" ||
    typeof name !== "string" ||
    typeof complete !== "boolean"
  ) {
    throw new Error("Form was not submitted Correctly");
  }
  await db.todo.create({ data: { name, description, complete } });
  return json({ msg: "Your form was submitted successfully." });
};

export default function Index() {
  const data = useLoaderData<todo[]>();
  const nav = useNavigation();
  const { msg } = useActionData() || {};

  toast(msg);

  return (
    <div>
      <h1>todo app</h1>
      <br />

      <ul>
        {data.map((d) => (
          <li key={d.id}>
            name:{d.name}
            <br />
            description:{d.description}
            <br />
            Complete{" "}
            <input type="checkbox" disabled={true} checked={d.complete} />
          </li>
        ))}
      </ul>
      <br />
      <h2>add</h2>
      <Form method="post">
        <label htmlFor="name">Name:</label>{" "}
        <input type="text" id="name" name="name" />
        <br />
        <label htmlFor="description">Description:</label>{" "}
        <textarea rows={5} id="description" name="description" />
        <br />
        <label htmlFor="complete">Complete</label>
        <input type="checkbox" id="complete" name="complete" />
        <button type="submit" disabled={nav.state !== "idle"}>
          Add
        </button>
      </Form>
    </div>
  );
}

根路由组件(root route)

import { Toaster } from "react-hot-toast";
import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction } from "@remix-run/node";
import {
  Links,
  LiveReload,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "@remix-run/react";

export const links: LinksFunction = () => [
  ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
];

export default function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <div role="status">
          <Toaster></Toaster>
        </div>
        <ScrollRestoration />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  );
}

排查思路

  • 修正toast调用时机:当前在组件顶层直接调用toast(msg),组件每次渲染都会触发。改用useEffect包裹,仅当msg存在且变化时触发:
    useEffect(() => {
      if (msg) {
        toast(msg);
      }
    }, [msg]);
    
  • 排查组件渲染次数:在Index组件中添加console.log("Index组件渲染"),查看是否渲染了8次——这会直接导致toast被调用8次。Remix在action完成、loader重新加载、导航状态变化时都会触发组件渲染。
  • 调整无障碍属性范围:react-hot-toast的Toaster本身可能已内置无障碍支持,外层的role="status"容器可能会监听内部所有DOM变更(比如toast的创建/销毁动画),导致多次播报。可以移除外层的role="status",或者给单个toast配置无障碍属性:
    toast(msg, {
      ariaProps: {
        role: "status",
        "aria-live": "polite"
      }
    });
    
  • 避免重复创建toast:给toast设置唯一ID,确保相同消息不会重复创建,而是替换现有toast:
    toast(msg, { id: "todo-submit-success" });
    
  • 用无障碍工具追踪播报源:打开Chrome开发者工具的「无障碍」面板,查看每次播报对应的DOM元素和变更事件,确认是role="status"容器的多次变更,还是toast组件的重复渲染导致的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 04:54:56