如何在回调函数中触发元素(使用状态)的重新渲染?

问题描述 投票:0回答:1

我目前正在使用 React (TSX),并遇到了有关状态的问题。我对这个话题相当陌生,所以如果这是明显需要解决的问题,我深表歉意..

考虑一个回调函数 testComponent,当按下按钮时,每次都会渲染一个新组件。

在此组件内,将有一个选择标签和一个输入标签,并且根据选择的内容,输入标签也必须更新。

例如,选择标签将显示对象的名称,输入标签将显示其 ID。

并且,所有 selectedOptions 都将以 options[0] 的默认值开始,这发生在 useEffect 中。

这是我的问题的重现,只是使用“测试”一词来代替,以便更容易理解(需要更少的上下文)。这个想法是,每次选择元素发生变化时,它也应该更新输入标签。

export default function CreateTestComponent() {
  const options: any[] = [
    {
      id: 1,
      name: "Test 1"
    },
    {
      id: 2,
      name: "Test 2",
    },
    {
      id: 3,
      name: "Test 3"
    }
  ]
  const [selectedOptions, setSelectedOptions] = useState<any[]>([options[0]]);
  const [testComponentIndex, setTestComponentIndex] = useState<number>(0);
  const [components, setComponents] = useState<any[]>([]);

  useEffect(() => {
    setComponents([testComponent(0)]);
    setTestComponentIndex((old) => old + 1);

    for (let i = 0; i < options.length; i++) {
      setSelectedOptions((old) => {
        const temp = [...old];
        temp[i] = options[0];
        return temp
      })
    }
  }, [])


  const testComponent = (index: number) => {

    return (
      <div className="flex flex-row gap-5" id={`${index}`}>
        <select
          onChange={((e) => {
            const id = e.target.value;
            setSelectedOptions((old) => {
              const temp = [...old]
              temp[index] = options.filter((option) => option.id == id)[0];
              return temp;
            })
          })}>
          {options.map((option, index: number) => {
            return (
              <option key={index} value={option.id}>
                {option.name}
              </option>
            );
          })}
        </select>
        <input readOnly value={selectedOptions[index].id} />
      </div>
    )
  }

  return (
    <>
      <button type="button" onClick={() => {
        setTestComponentIndex((old) => old + 1)
        setComponents([...components, testComponent(testComponentIndex)]);
      }} className="bg-black text-white rounded px-3 py-1">
        Add a Component
      </button>
      <div>
        <h1>Component testing!</h1>
        <div>
          <ul className="list-none">
            {components.map((component: any, index: number) => {
              return (
                <li key={index}>
                  <div className="flex flex-row gap-5">
                    {component}
                  </div>
                </li>
              )
            })}
          </ul>
        </div>
      </div>
    </>
  )
}

此代码将在 .tsx 文件中运行。

如您所见,状态正在更新,但输入标签没有更新。我做了大量令人烦恼的研究,试图弄清楚发生了什么,而且我非常确定,因为它位于回调函数内,其中状态不会持续更新(这反过来会触发重新渲染)。

我尝试做很多事情来绕过这个障碍。也就是说,我尝试了 useRef(),但它不具有重新渲染的功能,而只有 useState 似乎可以做到这一点。

我经历了很多其他事情,但没有一个解决了这个问题,因为他们都没有解决这个问题,即回调函数外部和内部的状态不同。

如果无法在回调函数中保持最新状态,我可以尝试哪些其他替代方法,以便我仍然能够按下按钮并每次生成组件的新实例?

谢谢

javascript reactjs typescript react-hooks react-tsx
1个回答
0
投票

我做了一些更改,应该可以正常工作。变化是:-

  • 将 TestComponent 分离为子组件,其中包含索引、selectedOption 和 handleSelectChange 函数的属性。

  • handleSelectChange 通过映射当前选项来更新状态中的正确选项。

  • AddComponent 将 TestComponent 的新实例添加到列表中,保持状态同步。

尝试一下,让我知道它是否适合你:)

import React, { useState, useEffect } from 'react';

interface Option {
  id: number;
  name: string;
}

const options: Option[] = [
  { id: 1, name: 'Test 1' },
  { id: 2, name: 'Test 2' },
  { id: 3, name: 'Test 3' },
];

const TestComponent = ({ index, selectedOption, handleSelectChange }: { index: number; selectedOption: Option; handleSelectChange: (index: number, id: number) => void }) => {
  return (
    <div className="flex flex-row gap-5" id={`${index}`}>
      <select
        value={selectedOption.id}
        onChange={(e) => handleSelectChange(index, parseInt(e.target.value))}
      >
        {options.map((option, idx) => (
          <option key={idx} value={option.id}>
            {option.name}
          </option>
        ))}
      </select>
      <input readOnly value={selectedOption.id} />
    </div>
  );
};

export default function CreateTestComponent() {
  const [selectedOptions, setSelectedOptions] = useState<Option[]>([options[0]]);
  const [components, setComponents] = useState<JSX.Element[]>([]);

  useEffect(() => {
    setComponents([<TestComponent key={0} index={0} selectedOption={options[0]} handleSelectChange={handleSelectChange} />]);
  }, []);

  const handleSelectChange = (index: number, id: number) => {
    setSelectedOptions((old) =>
      old.map((opt, idx) => (idx === index ? options.find((option) => option.id === id)! : opt))
    );
  };

  const addComponent = () => {
    setSelectedOptions([...selectedOptions, options[0]]);
    setComponents((old) => [
      ...old,
      <TestComponent key={old.length} index={old.length} selectedOption={options[0]} handleSelectChange={handleSelectChange} />,
    ]);
  };

  return (
    <>
      <button type="button" onClick={addComponent} className="bg-black text-white rounded px-3 py-1">
        Add a Component
      </button>
      <div>
        <h1>Component testing!</h1>
        <div>
          <ul className="list-none">
            {components.map((component, index) => (
              <li key={index}>
                <div className="flex flex-row gap-5">{component}</div>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </>
  );
}
© www.soinside.com 2019 - 2024. All rights reserved.