Home > Software engineering >  setState in React not updating my rendered catalog
setState in React not updating my rendered catalog

Time:10-05

I have a shopping catalog where the checkboxes filter which products are rendered.

The filters list is the menu and also passed in as a state.

The products list is the actual products which are rendered after filtered by looking at which category is checked or selected as "true".


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>

  )
}

In the handler for the checkbox, I copy the state, make my change, then update the state with the copy.

There are two issues:

  1. The products rendered does not change.
  2. It appears editing the copy changes the state itself.
...

function handleChangeMenu(event) {
    let newSelected = menuFilters

    newSelected.forEach(filterChange => {
      if (filterChange.name == event.target.name) {
       filterChange.selected = event.target.checked
      }
    })
    setMenuFilters(newSelected)
  }
}

...

All the solutions I've read so far say I have to use the setState which I do but the products still don't render.

I'm using Next JS.

CodePudding user response:

You are mutating the filters state, and then saving the same reference back into state, so React is bailing on rerenders.

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
}

You should shallow copy the previous state into a new object state reference for React to "see" that state has updated and trigger a rerender. Use a functional state update to shallow copy (map) the previous state to the next, and when updating the specific filter object, shallow copy it into a new object reference.

function handleChangeMenu(event) {
  const { checked, name } = event.target;
  setMenuFilters(filters => filters.map(
    filter => filter.name === name
      ? {
        ...filter,
        selected: checked,
      }
      : filter)
  )
}

CodePudding user response:

You are mutating state, instead treat it as immutable:

function handleChangeMenu(event) {
  const {name, checked } = event.target
  setMenuFilters(prevFilters => 
    prevFilters.map((filter) => {
      return filter.name === name
        ? { ...filter, selected: checked }
        : filter;
    })
  );
}
  • Related