фильтр и замена состояния массива с помощью setState не работают

#javascript #reactjs

#javascript #reactjs

Вопрос:

В настоящее время я создаю приложение React, которое будет принимать пользовательский ввод, перебирать и фильтровать данные массива, содержащие этот ввод, и изменять другое поле тех же данных. Однако, когда я использую setState, он показывает, что он не работает, и я не могу изменить поле. Другая проблема, с которой я столкнулся, заключается в том, что когда я пытаюсь отобразить данные в таблице с помощью .map, при использовании setState для изменения данных отображается ошибка. Приветствуется любая информация о том, как сделать эту работу.

  const [data, setData] = useState([]);
 

Вот как я извлекаю данные из своей базы данных.

  useEffect(() => {
    fetch("/edit").then(res => {
        if(res.ok) {
            return res.json();
        }
    })
    .then(jsonRes => setData(jsonRes))
},[])
 

Это моя функция фильтра, которая активируется при нажатии кнопки. q — это пользовательский ввод, множественная консоль.журнал используется для проверки того, работает ли каждый шаг, и хотя кажется, что каждый шаг работает, и console.log(CopyData[i]) показывает, что заменяемое поле заменяется, оно не работает после того, как я поместил его в setData.

  const filter = (e) => {
  e.preventDefault();
  console.log(q)
  const pattern = new RegExp("\b" q "\b", "ig")
  console.log(pattern)
  console.log(data.length)
  for (var i =0; i < data.length; i  ){
    if(data[i].diseaseName.search(pattern) > -1){
      console.log(data[i])
      let copyData = {...data}
      console.log(copyData)
      let replaceData = {
        ...copyData[i],
        vector: 'testing filter'
      }
      console.log(replaceData)
      copyData[i]=replaceData
      console.log(copyData[i])
      setData(copyData)
    }
  }
}
 

Это моя таблица, которая сначала работает, но после setData она выдает ошибку и говорит, что data.map не является функцией

  <table className="content-table">
        <thead>
          <tr>
            <th>Name</th>
            <th>Species</th>
            <th>Vector</th>
            <th>Agent</th>
          </tr>
        </thead>
        {data.map(data=> 
        <tbody>
        
        <tr key={data._id} data-item={data} >
            <td  data-title="name">{data.diseaseName}</td>
            <td  data-title="species">{data.species}</td>
            <td  data-title="vector">{data.vector}</td>
            <td  data-title="agent">{data.agent}</td>
        </tr>
        </tbody>
        )}
        </table>
 

Ответ №1:

Проблема связана с этой строкой в вашей функции фильтра let copyData = {...data} . data это массив, но вы копируете его в объект. Это работает просто отлично. Ключами объекта будут индексы массива (0, 1, 2 …). Но объекты не имеют методов массива map , filter , и т.д. reduce Вот почему вы видите ошибку «data.map не является функцией».

Изменить let copyData = {...data} на let copyData = [...data]

Обновление — Дополнительные мысли

  1. Вот как я бы написал ваш метод фильтрации. Вы хотите избежать вызова setData из цикла for. Это приведет к повторному рендерингу на каждой итерации. Вместо этого создайте новый массив данных, а затем вызовите setData его один раз.
 const filter = (e) => {
  e.preventDefault();
  const pattern = new RegExp("\b" q "\b", "ig");
  const newData = data
    .filter(d => d.diseaseName.search(pattern) > -1))
    .map(d => ({ ...d, vector: 'testing filter' }));
  setData(newData);
}
 
  1. setData также может быть вызван следующим образом ( prevInput я думаю, вы упомянули в своих комментариях): setData(prev => prev.filter(d => d.diseaseName.search(pattern) > -1))

Это было бы полезно, если бы вы включили в него свой метод фильтрации useCallback .

 const filter = useCallback((e) => {
  e.preventDefault();
  const pattern = new RegExp("\b" q "\b", "ig");
  setData(prev => prev.filter(d => d.diseaseName.search(pattern) > -1));
}, [q]);
 

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

  1. В общем, я бы не стал изменять исходный массив данных, а создал бы производное значение для отфильтрованного массива с useMemo помощью .
 const [data, setData] = useState([]);
const [q, setQ] = useState('');

const filteredData = useMemo(() => {
  const pattern = new RegExp("\b" q "\b", "ig");
  return data.filter(d => d.diseaseName.search(pattern) > -1));
}, [data, q]);
 

Таким образом, нет необходимости в обязательном вызове функции filter . Все синхронизируется, и вы можете ссылаться на исходные и отфильтрованные массивы по мере необходимости.

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

1. Привет, спасибо за это исправление, на самом деле это все, что потребовалось, однако теперь у меня есть другая проблема, которая заключается в том, что при выполнении цикла он заменяет только последние данные, которым он соответствует, я предполагаю, что это связано с тем, что для замены требуются старые данные, которые не были обновлены до момента завершения цикла. происходящее и предыдущий матч уже были заменены. У меня есть небольшая идея, что я могу использовать prevInput, чтобы исправить это, но я не уверен, как, вы можете мне помочь с этим?

2. Добавлены некоторые альтернативные реализации, которые, я думаю, могут помочь.

3. Большое Вам спасибо за все предложения! Это дало мне много идей о том, как продолжить мой проект, однако я не могу использовать метод фильтрации, который вы использовали в своем примере. Это связано с тем, что я хочу, чтобы пользователь мог выполнять несколько фильтров и отменять поиск. Мой метод сделать это — увеличивать или уменьшать поле с оценкой каждый раз, когда данные совпадают с вводимыми, или пользователь удаляет фильтр, и использовать другой фильтр для отображения данных, которые соответствуют количеству общих фильтров (извините, если это сбивает с толку), поэтому мне нужны все данные без изменений. Вот почему я использую цикл для редактирования только один раз, когда это соответствует

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

5. Вот как я пытался сделать это со своей идеей prevData, но это не работает. setData( (prevData) => ({ data: [ [...prevData], { ...prevData[i], matched: } ] }))

Ответ №2:

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

   for (var i =0; i < data.length; i  ){
    if(data[i].diseaseName.search(pattern) > -1){
      console.log(data[i])
      let copyData = {...data}
      console.log(copyData)
      let replaceData = {
        ...copyData[i],
        vector: 'testing filter'
      }
      console.log(replaceData)
      copyData[i]=replaceData
      console.log(copyData[i])
      setData(copyData)
    }
  }
 

можете ли вы вместо этого выполнить метод фильтрации данных, а затем вызвать setData? выполнение этого в цикле for может привести к дополнительным рендерингам, и фильтр должен безопасно возвращать массив примерно так

 const filteredData = data.filter((disease) => disease.diseaseName.search(pattern) > -1)
setData(filteredData)
 

кроме того, с точки зрения доступности html вы действительно не хотите сопоставлять свои данные с несколькими tbody тегами

     <tbody>
     {data.map(data=>(
        <tr key={data._id} data-item={data} >
          <td  data-title="name">{data.diseaseName}</td>
          <td  data-title="species">{data.species}</td>
          <td  data-title="vector">{data.vector}</td>
          <td  data-title="agent">{data.agent}</td>
        </tr>
       )
     )}
    </tbody>
 

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

1. эй, спасибо за это предложение, я действительно использовал этот метод до того, как попробовал цикл. Однако для моего приложения я хочу, чтобы пользователь мог выполнять несколько фильтров и отменять поиск. Мой метод сделать это — увеличивать или уменьшать поле с оценкой каждый раз, когда данные совпадают с вводимыми, или пользователь удаляет фильтр, и использовать другой фильтр для отображения данных, которые соответствуют количеству общих фильтров (извините, если это сбивает с толку), поэтому мне нужны все данные без изменений. Метод, который вы предоставили, был намного чище, поэтому, если у вас есть какие-либо идеи, как реализовать его в моем приложении, пожалуйста, дайте мне знать