setState в React не обновляет мой отрисованный каталог

#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>

  )
}

 

В обработчике для флажка я копирую состояние, вношу свои изменения, затем обновляю состояние копией.

Есть две проблемы:

  1. Отображаемые продукты не меняются.
  2. Похоже, что редактирование копии изменяет само состояние.
 ...

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;
    })
  );
}