Массив в состоянии реакции обновляется неправильно

#reactjs

Вопрос:

Я в основном пытаюсь обновить фильтр элементов из массива «Все местоположения» в массив «Выпадающее местоположение», но он обновляется неправильно. при первом изменении в поле ввода он неправильно обновляет массив, а при втором изменении он его не обновляет.

он показывает мне этот вывод, но я не знаю, почему это так

  import logo from "./logo.svg";
        import "./App.css";
        import { useState, useEffect } from "react";
        
    function App() {
      // location entered by the user
      const [location, setlocation] = useState("");
    
      const [allLocations, setallLocations] = useState(["F-1", "F-2", "G-1", "G-2"]);
    
      const [dropDownLocations, setdropDownLocations] = useState([]);
    
      const filterLocations = (userInput) => {
        console.log("user input ", location);
    
        allLocations.map((i) => {
          if (i.includes(location)) {
            console.log("true at ", i);
            setdropDownLocations([...dropDownLocations, i]);
          } else {
            setdropDownLocations([]);
          }
        });
    
        console.log("after map ", dropDownLocations);
      };
    
      return (
        <div className="App">
          <div>
            <input
              value={location}
              onChange={(e) => {
                setlocation(e.target.value);
                filterLocations(e.target.value);
              }}
            />
            <ul>
              {dropDownLocations.map((i) => (
                <li key={i}>{i}</li>
              ))}
            </ul>
          </div>
        </div>
      );
    }
    
    export default App;
 

Ответ №1:

Вам не нужно усложнять это, просто отфильтруйте массив на основе ввода пользователя

 const filterLocations = (userInput) => {
  setdropDownLocations(
    allLocations.filter((location) => location.includes(userInput))
  );
};
 

Я упростил вам задачу в этом рабочем примере:

Редактировать пустую архитектуру-l0q8l

Комментарии:

1. Спасибо, это нормально работает, но у меня есть еще 1 проблема, проблема в том, что вы все сначала набираете что-то, что работает нормально, но когда я стираю весь введенный текст, он показывает весь отображаемый элемент.

2. Что это такое? Он не фильтруется, когда входные данные пусты?

3. да, он не фильтруется, когда мы очищаем поле ввода

Ответ №2:

Это setState асинхронная функция, и поэтому ваша текущая реализация работает неправильно, так как вы пытаетесь прочитать state ее до ее обновления.

Обновите свою filterLocations функцию следующим образом:

 const filterLocations = (e) => {
  const location = e.target.value;
  const filteredLocation = allLocations.filter(i => i.includes(location));
  setlocation(location);
  setdropDownLocations(filteredLocation)
};
 

И обновите свой input тег следующим образом:

 <input value={location} onChange={filterLocations} />
 

Ответ №3:

Это не работает, потому что для каждого местоположения вы устанавливаете раскрывающееся местоположение, и если оно не содержит местоположения, вы снова устанавливаете его в пустой массив [].

        allLocations.map((i) => {
          if (i.includes(location)) {
            console.log("true at ", i);
            setdropDownLocations([...dropDownLocations, i]);
          } else {
            setdropDownLocations([]);
          }
        });
 

Лучшим подходом было бы:

 setDropDownLocation([...allLocations].filter((i) => i.includes(userInput))
 

Ответ №4:

Есть некоторые ошибки в том, что вы сделали, я внес некоторые изменения, попробуйте запустить код, который я написал.

 import logo from "./logo.svg";
import "./App.css";
import { useState, useEffect } from "react";

const ALL_LOCATIONS = ['F-1', 'F-2', 'G-1', 'G-2'];

function App() {
  // location entered by the user
  const [location, setLocation] = useState("");
  const [dropDownLocations, setDropDownLocations] = useState([]);

  function onLocationInputChange(event){
    setLocation(event.target.value);

    setDropDownLocations(ALL_LOCATIONS.filter((item)=>item.includes(event.target.value)))
  }

  return (
    <div className="App">
      <div>
        <input value={location} onChange={onLocationInputChange} />
        <ul>
          {dropDownLocations.map((loc) => (
            <li key={`${location}-${loc}`}>{loc}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default App; 

Комментарии:

1. @Сами Малик, я надеюсь, что это вам поможет!!

2. Мы всегда рады Вам @SamiMalik.

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

4. Спасибо, что спросили ! когда мы обычно выполняем вызовы Api в этом случае, требуется немного времени, когда на самом деле асинхронные функции играют хорошую роль, но здесь не та сцена, и если мы посмотрим на React, как только состояние обновляется, React отображает компонент, поэтому в соответствии с нашим кодом пользовательский интерфейс обновляется.

5. @SamiMalik вы новичок в React?

Ответ №5:

Это вызвано тем фактом, что ваш onChange обработчик определен прямо в JSX, в результате чего React создает новую функцию при каждом рендеринге (то же самое относится и к filterLocations одному).

Вы всегда должны пытаться извлечь каждую часть логики JS за пределами компонента или, по крайней мере, запомнить их, вот как:

 import React, { useState, useCallback } from "react";

import logo from "./logo.svg";
import "./App.css";

const ALL_LOCATIONS = ['F-1', 'F-2', 'G-1', 'G-2'];

function App() {
  // location entered by the user
  const [location, setLocation] = useState("");
  // locations shown to the user in dropdown (filterable)
  const [dropDownLocations, setDropDownLocations] = useState([]);

  const onLocationInputChange = useCallback(
    (ev) => {
      // In case no target passed to callback, do nothing
      if (!ev || !ev.target || !ev.target.value) {
        return;
      }

      const userInput = ev.target.value;

      // Filter so that if user input matches part of the location
      // it gets not filtered out
      setDropDownLocations([
        ...ALL_LOCATIONS.filter(
          (loc) =>
            loc.startsWith(userInput) ||
            loc.endsWith(userInput) ||
            loc.indexOf(userInput) !== -1
        ),
      ]);

      // Finally update the location var
      setLocation(userInput);
    },
    [setDropDownLocations]
  );

  return (
    <div className="App">
      <div>
        <input value={location} onChange={onLocationInputChange} />
        <ul>
          {dropDownLocations.map((loc) => (
            <li key={`${location}-${loc}`}>{loc}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default App;