Как я могу вызвать повторную отрисовку элемента (который использует состояние) в функции обратного вызова?

В настоящее время я использую React и столкнулся с проблемой, связанной с состояниями.

Рассмотрим функцию обратного вызова testComponent, которая при нажатии кнопки каждый раз отображает новый компонент.

Внутри этого компонента будут элементы select и input, и в зависимости от того, что выбрано, элемент input также должен обновиться.

Элемент select будет отображать имя объекта, а элемент input — его идентификатор.

Все selectedOptions начинаются со значения по умолчанию options[0], которое происходит в useEffect.

Это воссоздание моей проблемы, в котором вместо этого используется слово «тест», чтобы его было легче понять (требуется меньше контекста). Идея состоит в том, что каждый раз, когда элемент select изменяется, он также должен обновлять значение input.

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.

Я прошел через кучу других вещей, но ни одна из них не обошла эту проблему, потому что никто из них не боролся с проблемой, заключающейся в том, что состояние снаружи и внутри функции обратного вызова не одно и то же.

Если нет способа обеспечить актуальное состояние внутри функции обратного вызова, какие еще альтернативы я мог бы попробовать, чтобы иметь возможность нажимать кнопку и каждый раз генерировать новый экземпляр компонента?

Вы сохраняете JSX в своем состоянии React, что приводит к устаревшим замыканиям Javascript. Вам следует только сохранять данные в состоянии, а затем сопоставлять их с JSX при рендеринге.

Drew Reese 13.08.2024 00:50

Извините, но что вы имеете в виду? Какие данные мне следует сохранить в состоянии, а затем сопоставить при рендеринге?

panda 13.08.2024 03:52

Сохраняйте любые данные, представляющие ваше «состояние», то есть все, что представляет собой пользовательский интерфейс, который вы хотите отобразить. Судя по вашему коду, состояние должно быть параметрами или массивами параметров, которые вы отображаете.

Drew Reese 13.08.2024 05:31
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
3
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я внес некоторые изменения, и все должно работать нормально. Изменения: -

  • отделенный 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>
    </>
  );
}

Нет, по какой-то причине это не работает... Изначально проблема заключалась в том, что когда я пытался выбрать другой вариант, он мне не позволял. я удалил value = {selectedOption.id}, и он позволил мне изменить выбранный вариант. Но он все еще не обновил входной тег. Почему?

panda 13.08.2024 03:52
Ответ принят как подходящий

Вы сохраняете JSX в своем состоянии React, что приводит к устаревшим замыканиям Javascript. Вам следует только сохранять данные в состоянии, а затем сопоставлять их с JSX при рендеринге. Вы также немного усложняете логику, используя несколько состояний для представления «данных», того, что добавляется и т. д. Ее можно свести к одному состоянию.

Пример рефакторинга:

  • Переместите данные статических параметров за пределы дерева React.
  • Вы используете Typescript, поэтому вам действительно следует использовать его и создавать полезные типы/интерфейсы.
  • Используйте одно состояние selectedOptions, которое представляет собой массив объектов со свойствами id и name, которые вы визуализируете.
  • Добавьте к данным свойство GUID, чтобы их всегда можно было однозначно идентифицировать.
  • Используйте обновления состояния для поверхностного копирования текущего состояния, которое вы обновляете, и возврата для следующего значения состояния.
  • При добавлении новых «компонентов» создайте новый объект данных, который представляет компонент/выбранный параметр, который вы хотите визуализировать.
import { useState } from "react";
import { nanoid } from "nanoid";

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

interface SelectedOption extends Option {
  _id: string;
}

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

const createOption = (): SelectedOption => ({
  _id: nanoid(),
  ...options[0],
});

function CreateTestComponent() {
  const [selectedOptions, setSelectedOptions] = useState<SelectedOption[]>([
    createOption(),
  ]);

  const renderOptions = (option: SelectedOption) => {
    return (
      <div className = "flex flex-row gap-5" id = {`${option._id}`}>
        <select
          onChange = {(e) => {
            const id = Number(e.target.value);
            setSelectedOptions((selectedOptions) =>
              selectedOptions.map((selectedOption) =>
                selectedOption._id === option._id
                  ? { ...selectedOption, id }
                  : selectedOption
              )
            );
          }}
        >
          {options.map((option) => (
            <option key = {option.id} value = {option.id}>
              {option.name}
            </option>
          ))}
        </select>
        <input readOnly value = {option.id} />
      </div>
    );
  };

  return (
    <>
      <button
        type = "button"
        onClick = {() => {
          setSelectedOptions((selectedOptions) =>
            selectedOptions.concat(createOption())
          );
        }}
        className = "bg-black text-white rounded px-3 py-1"
      >
        Add a Component
      </button>
      <div>
        <h1>Component testing!</h1>
        <div>
          <ul className = "list-none">
            {selectedOptions.map((option: SelectedOption) => {
              return (
                <li key = {option._id}>
                  <div className = "flex flex-row gap-5">
                    {renderOptions(option)}
                  </div>
                </li>
              );
            })}
          </ul>
        </div>
      </div>
    </>
  );
}

Ох, я вижу! Это имеет большой смысл! Вы сопоставляете массив выбранных параметров, а затем визуализируете компонент вместо того, что я делал, а именно сопоставлял компоненты (которые находились в устаревшем состоянии) для их рендеринга. Это здорово, большое спасибо за вашу помощь!

panda 13.08.2024 17:19

@Панда Да. Добро пожаловать, рад помочь. Здоровья и удачи!

Drew Reese 13.08.2024 17:20

Другие вопросы по теме