#javascript #reactjs #next.js
Вопрос:
У меня есть каталог покупок, где флажки фильтруют, какие товары отображаются.
filters
Список является меню и также передается как состояние.
products
Список-это фактические продукты, которые отображаются после фильтрации, если посмотреть, какая категория проверена или выбрана как «истинная».
const filters = [
{
"name": "Food",
"selected": true
},
{
"name": "Books",
"selected": true
},
{
"name": "Cars",
"selected": true
}
]
const products = [
{
"name": "Sandwich",
"catagory": "Food",
"price": 2
},
{
"name": "Soup",
"catagory": "Food",
"price": 5
},
{
"name": "Toyota",
"catagory": "Cars",
"price": 25
},
{
"name": "Tesla",
"catagory": "Cars",
"price": 100
},
{
"name": "BMW",
"catagory": "Cars",
"price": 69420
}
]
export default function TestCatalog( props ) {
const [menuFilters, setMenuFilters] = useState(filters)
function handleChangeMenu(event) {
let newSelected = menuFilters
newSelected.forEach(filterChange => {
if (filterChange.name == event.target.name) {
filterChange.selected = event.target.checked
}
})
setMenuFilters(newSelected)
}
function buildMenuItems(items) {
return (
items.map(value =>
<FormControlLabel control={<Checkbox defaultChecked />}
name={value.name}
onChange={event => handleChangeMenu(event)}
label={value.name} />
)
)
}
function buildCatalogGrid() {
let listItemsSelected = []
menuFilters.map(obj => {
if (obj.selected) {
listItemsSelected.push(obj.["name"])
}
})
let productsToRender = products.filter(
product => listItemsSelected.includes(product.catagory)
)
return (
productsToRender.map(value =>
<Grid item xs={12} sm={6} md={4}>
<CatalogCard title={value.name}
body={value.price}>
</CatalogCard>
</Grid>
)
)
}
return (
<Grid container>
<Grid item>
<FormGroup>
{buildMenuItems(filters)}
</FormGroup>
</Grid>
<Grid item>
<Grid container>
{buildCatalogGrid()}
</Grid>
</Grid>
</Grid>
)
}
В обработчике для флажка я копирую состояние, вношу свои изменения, затем обновляю состояние копией.
Есть две проблемы:
- Отображаемые продукты не меняются.
- Похоже, что редактирование копии изменяет само состояние.
...
function handleChangeMenu(event) {
let newSelected = menuFilters
newSelected.forEach(filterChange => {
if (filterChange.name == event.target.name) {
filterChange.selected = event.target.checked
}
})
setMenuFilters(newSelected)
}
}
...
Все решения, которые я читал до сих пор, говорят, что я должен использовать setState, что я и делаю, но продукты все еще не отображаются.
Я использую Next JS.
Ответ №1:
Вы изменяете filters
состояние, а затем сохраняете ту же ссылку обратно в состояние, поэтому React отказывается от повторных отправителей.
function handleChangeMenu(event) {
let newSelected = menuFilters // <-- reference to state
newSelected.forEach(filterChange => {
if (filterChange.name == event.target.name) {
filterChange.selected = event.target.checked // <-- state mutation!!
}
})
setMenuFilters(newSelected) // <-- reference saved back into state
}
Вы должны скопировать предыдущее состояние в ссылку на новое состояние объекта для реакции, чтобы «увидеть», что состояние обновлено, и вызвать повторную отправку. Используйте обновление функционального состояния для неглубокого копирования (сопоставления) предыдущего состояния со следующим, а при обновлении конкретного объекта фильтра неглубоко скопируйте его в новую ссылку на объект.
function handleChangeMenu(event) {
const { checked, name } = event.target;
setMenuFilters(filters => filters.map(
filter => filter.name === name
? {
...filter,
selected: checked,
}
: filter)
)
}
Комментарии:
1. Это прекрасно! Спасибо! Почему вы разбиваете событие.цель? Я попытался назначить, как в вопросе, и получил ошибки, ваши сработали.
2. @engineer-x Ah, в зависимости от версии React объекты событий объединяются в пул и (как правило ) быстро аннулируются. Это иногда может вызвать проблемы, когда вы ставите в очередь обновления состояния и имеете дело с асинхронным характером фактического обновления состояния. Разрушая значения событий перед обновлением состояния, вы закрываете их в области обратного вызова, и не имеет значения, если/когда объект события будет обнулен и возвращен в пул событий. Я считаю, что в React 17 это не проблема. Я думаю, что это также более чистый код. 😉
Ответ №2:
Вы мутируете состояние, вместо этого относитесь к нему как к неизменному:
function handleChangeMenu(event) {
const {name, checked } = event.target
setMenuFilters(prevFilters =>
prevFilters.map((filter) => {
return filter.name === name
? { ...filter, selected: checked }
: filter;
})
);
}