Home > Software engineering >  React - how to make properly working computed properties?
React - how to make properly working computed properties?

Time:11-23

I am now trying to make table with checkboxes, and got really irritating problem. I reproduced this problem in this example.

I have list of items of format {id, value}. For each item I create div element with checkbox and text inside. When I want to make item selected, I add item id into selectedItems list.
The problem is that I can't make component checkbox update its state on selectedItems list change.

Tell me please, how to do it normally? In Vue, I could just create computed property for each sub-component and pass selectedItems and binded prop. How to do it in React?

import {Checkbox} from "@mui/material";
import {useMemo, useState} from "react";

export default function Example() {
  const items = useState([{id: 0, value: "Item 1"}, {id: 1, value: "Item 2"}, {id: 2, value: "Item 3"}])
  const [selectedItems, setSelectedItems] = useState([])
  console.log(items)

  function ListItems({items}) {
    return useMemo(() => {
      const elements = []
      for (let i = 0; i < items[0].length; i  ) {
        const item = items[0][i]
        const checked = selectedItems.indexOf(item.id) !== -1
        console.log(item, items.length)
        elements.push(
          <div key={i}>
            <Checkbox
              checked={checked}
              onChange={() => {
                let state = selectedItems
                if (checked) {
                  state = state.filter(id => id !== item.id)
                } else {
                  state.push(item.id)
                }
                setSelectedItems(state)
              }}
            />
            <p>{item.value}</p>
          </div>
        )
      }

      return elements
    }, [items])
  }

  return (
    <div style={{display: "flex", flexDirection: "column"}}>
      <ListItems items={items}/>
    </div>
  )
}

Codesandbox: https://codesandbox.io/s/not-working-computed-props-react-forked-fywtxp?file=/src/App.js

CodePudding user response:

Apart from my comment about selectedItems that should be provided in useMemo dependencies, you have other issue, which is that you don't modify your state really here setSelectedItems(state)

Arrays are being compared by reference, so if you have const x = y = []; and then x.push('1'), x is still equal to y. Hence when you do

let state = selectedItems
...
state.push(...)
...
setSelectedItems(state)

It doesn't trigger re-render cause state is still equals to previous one (to selectedItems)

The fix could be either

state = [...state, item.id] instead of push

Or setSelectedItems([...state])

  • Related