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

React动态Checklist组件中的输入框状态重置与空格字符处理问题

React动态Checklist组件中的输入框状态重置与空格字符处理问题

我正在开发一个React组件来管理准备清单,用户可以添加、删除和保存项目。大部分功能都正常工作,但遇到两个问题:

输入框状态重置问题

当添加新的清单项目时,输入字段(newField)有时会意外重置,这可能是由于组件重新渲染导致的。当我更新items状态时,整个组件会重新渲染,从而引发这个问题。

空格字符处理问题

输入内容开头或结尾的空格没有被正确处理。

代码片段

清单组件代码

"use client";

import { useState, useTransition } from "react";

import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { LoaderSpinner } from "@/components/loader-spinner";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { delay } from "@/lib/utils";
import { XIcon, Check, CircleMinus } from "lucide-react";
import { prepCheckList } from "@/lib/utils";
import { useRouter } from "next/navigation";

export function PreparationChecklist({
  isChecklistOpen,
  setIsChecklistOpen,
  entityId,
  data,
}) {
  const [isPending, startTransition] = useTransition();

  const [isOpen, setIsOpen] = useState(false);
  const [items, setItems] = useState(
    data?.length > 0
      ? data
      : [
          {
            label: "label 1",
            checked: false,
            isNew: false,
          },
          {
            label: "label 2",
            checked: false,
            isNew: false,
          },
          {
            label: "label 3",
            checked: false,
            isNew: false,
          },
          { label: "label 4", checked: false, isNew: false },
          {
            label: "label 5",
            checked: false,
            isNew: false,
          },
          {
            label: " label 6",
            checked: false,
            isNew: false,
          },
        ],
  );
  const [newField, setNewField] = useState("");
  const [isSaving, setIsSaving] = useState(false);
  const [isAdding, setIsAdding] = useState(false);

  const route = useRouter();

  const handleCheckedChange = (index, checked) => {
    setItems((prevItems) => {
      const updatedItems = [...prevItems];
      updatedItems[index].checked = checked;
      return updatedItems;
    });
  };

  const handleSave = async () => {
    setIsSaving(true);
    const response = await prepCheckList(entityId, items);
    console.log("response: >>>>>>>>", response);

    await delay(1000); // Simulate API call
    startTransition(() => {
      route.refresh();
      setIsSaving(false);
          });
  };

  const handleAddMoreClick = () => {
    if (isAdding) {
      if (newField) {
        // Add new item if input is not empty
        setItems((prevItems) => [
          ...prevItems,
          { label: newField, checked: false, isNew: true },
        ]);
        setNewField(""); // Reset the input field
        setIsAdding(false); // Hide the input field
      } else {
        // Hide input field if clicked again with no input
        setIsAdding(false);
      }
    } else {
      // Show input field
      setIsAdding(true);
    }
  };
  const handleDeleteField = (index) => {
    // Only allow deleting items with `isNew: true`
    setItems((prevItems) =>
      prevItems.filter((_, i) => !(i === index && prevItems[i].isNew)),
    );
  };
  return (
   
      
      <Dialog
      open={isChecklistOpen}
      onOpenChange={() => setIsChecklistOpen(!isChecklistOpen)}
    >
        <DialogContent className="sm:max-w-[900px]">
          <DialogHeader>
            <DialogTitle className="text-xl">
              Checklist
            </DialogTitle>
          </DialogHeader>
          <div className="ml-2">
            <ScrollArea className="h-[250px] rounded-md">
              {items.map((item, index) => (
                <div
                  key={`${index}-${item.label}`}
                  className="flex items-center gap-3 py-2"
                >
                  <Checkbox
                    id={`${index}-${item.label}`}
                    checked={item.checked}
                    onCheckedChange={(checked) =>
                      handleCheckedChange(index, checked)
                    }
                    className="h-4 w-4"
                  />
                  <Label
                    htmlFor={`${index}-${item.label}`}
                    className="cursor-pointer"
                  >
                    {item.label}
                  </Label>
                  <div className="flex w-6 justify-center">
                    {item.isNew && (
                      <Button
                        variant="link"
                        size="smIcon"
                        onClick={() => handleDeleteField(index)}
                      >
                        <CircleMinus className="h-4 w-4 text-red-500 hover:text-red-600" />
                      </Button>
                    )}
                  </div>
                </div>
              ))}
            </ScrollArea>
          </div>
          <div className="mt-4 flex items-center justify-end gap-4">
            {isAdding && (
              <Input
                value={newField}
                onChange={(e) => setNewField(e.target.value)}
                placeholder="Add new checklist item"
                className="flex-1"
              />
            )}
            <Button
              size={isAdding ? "icon" : "sm"}
              variant={isAdding ? "outline" : "TypeA"}
              onClick={handleAddMoreClick}
              className={isAdding ? "rounded-full" : ""}
            >
              {isAdding ? (
                newField ? (
                  <Check className="h-5 w-5 text-green-500 hover:text-green-600" />
                ) : (
                  <XIcon className="h-5 w-5 text-red-500 hover:text-red-600" />
                )
              ) : (
                "Add More"
              )}
            </Button>
            <Button
              size="sm"
              variant="TypeA"
              onClick={handleSave}
              disabled={isSaving || isPending}
            >
              {isSaving || isPending ? (
                <LoaderSpinner className="text-white" />
              ) : (
                "Save changes"
              )}
            </Button>
          </div>
          <DialogFooter></DialogFooter>
        </DialogContent>
      </Dialog>
   
  );
}

Input组件封装代码

import * as React from "react";

import { cn } from "@/lib/utils";
const Input = React.forwardRef(({ className, type, ...props }, ref) => {
  return (
    <input
      type={type}
      className={cn(
        "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", // placeholder:text-muted-foreground | placeholder-neutral-500
        className,
      )}
      ref={ref}
      {...props}
    />
  );
});
Input.displayName = "Input";

export { Input };

我尝试过的方法

  • 防抖输入处理:我尝试过对输入处理函数做防抖,但字段在重新渲染时仍然会重置。

期望行为

  • 输入字段(newField)即使在items状态更新导致组件重新渲染时,也应该保留其值。
  • 用户可以不受限制地输入空格(开头、结尾或中间)。

问题

  1. 如何防止输入字段(newField)在重新渲染时重置?
  2. 在确保验证干净的前提下,处理输入字段中空格的最佳方式是什么?

任何建议或替代方法都将不胜感激!谢谢。

备注:内容来源于stack exchange,提问作者Rafiul Rafi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 15:09:05