#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]
Обновление — Дополнительные мысли
- Вот как я бы написал ваш метод фильтрации. Вы хотите избежать вызова
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);
}
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
его не нужно указывать в массиве зависимостей, и функцию фильтра не нужно так часто создавать заново.
- В общем, я бы не стал изменять исходный массив данных, а создал бы производное значение для отфильтрованного массива с
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. эй, спасибо за это предложение, я действительно использовал этот метод до того, как попробовал цикл. Однако для моего приложения я хочу, чтобы пользователь мог выполнять несколько фильтров и отменять поиск. Мой метод сделать это — увеличивать или уменьшать поле с оценкой каждый раз, когда данные совпадают с вводимыми, или пользователь удаляет фильтр, и использовать другой фильтр для отображения данных, которые соответствуют количеству общих фильтров (извините, если это сбивает с толку), поэтому мне нужны все данные без изменений. Метод, который вы предоставили, был намного чище, поэтому, если у вас есть какие-либо идеи, как реализовать его в моем приложении, пожалуйста, дайте мне знать