yavg
yavg

Reputation: 3051

Checking the checkbox in a list does not render again in React

I am trying to change its value when you click on a checkbox. But for some reason the changes are not rendered again. why?

enter image description here

sample data (props.data):

    {
        "school": {
            "checkDocuments": [
                {
                    'label': 'license',
                    'check': false
                },
                {
                    'label': 'identification',
                    'check': false
                },
                {
                    'label': 'phone',
                    'check': false
                },

            ],
            "section": "escuela"
        },

        "collage": {
            "checkDocuments": [
                {
                    'label': 'license',
                    'check': false
                }
            ],
            "section": "universidad"
    }

this is my component:

export const Verificardocuments = props => {

    const [documents, setDocuments] = useState(props.data);

    updateDataDocument = (section, index) => {
      console.log(section, index);
      documents[section].checkDocuments[index].check = 
      !documents[section].checkDocuments[index].check;
      setDocuments(documents);
    }

   return 
    <List>

    {
        /* escuela,universidad  => "section" key */
        Object.keys(documents).map((section, i) => {

            return (
                <View key={i}>
                    <ListItem itemDivider >
                        <Text>{documents[section].section}</Text>
                    </ListItem>

                    {documents[section].checkDocuments.map((document, j) => {
                        return (
                            <ListItem onPress={() => updateDataDocument(section, j)} key={j} >
                                <CheckBox checked={document.check} color="blue" />
                                <Body>
                                    <Text>{document.label}</Text>
                                </Body>
                            </ListItem>)
                    })}
                </View>
            )

        })
    }
    </List>
 }

I am not sure if I am doing the best way to update an element contained in my array.

Upvotes: 0

Views: 277

Answers (3)

Asaf Aviv
Asaf Aviv

Reputation: 11790

State updates in function components must be immutable.

It is recommended to use the callback pattern when updating objects inside the state and spread each object to avoid losing the rest of the properties

updateDataDocument = (section, index) => {
  setDocuments(prevState => ({
    // spread the previous state into a new object
    ...prevState,
    [section]: {
      // also spread prevState[section] into a new object
      ...prevState[section],
      // create a new array of checkDocuments
      checkDocuments: prevState[section].checkDocuments.map((doc, i) =>
        i === index
          ? // when we find the object we need to update,
            // spread it into a new object and then update
            // the check property
            { ...doc, check: !doc.check }
          : doc
      ),
    },
  }))
}

Nested immutable updates are ugly and it might be better to introduce an immutable library that will handle it for you such as immer which have a hooks version which lets you update the state in a mutable way

import { useImmer } from 'use-immer'

export const Verificardocuments = props => {
  const [documents, setDocuments] = useImmer(props.data);

  const updateDataDocument = (section, index) => {
    setDocuments(draft => {
      const prevChecked = draft[section].checkDocuments[index].check
      draft[section].checkDocuments[index].check = !prevChecked
    })
  }

  return ...
}

Upvotes: 1

Chris
Chris

Reputation: 59511

Here's one way to do it.

Since your data structure is somewhat complex/big, you need to create a copy for every nested object or array within. Once that is done, you can safely update the copy without also changing the original variable (the state). Then, with the copy being updated to how you want the new state to be, you simply use it to set the new state.

updateDataDocument = (section, index) => {
  const obj = {...documents}; // Copy the documents object
  const arr = [...obj[section].checkDocuments] // Copy the checkDocuments array
  const item = {...arr[index]} // Copy the item inside the array
  item.check = !item.check; // Change the value
  arr[index] = item;
  obj[section].checkDocuments = arr;
  setDocuments(obj);
}

Another way, which effectively is the same thing, is how @AsafAviv did it.

Upvotes: 1

albert
albert

Reputation: 474

documents is being mutated that's why it doesn't trigger re-render. so i advise to assign it to a local copy and do the mutation there

updateDataDocument = (section, index) => {
  const newDoc = documents
  newDoc[section].checkDocuments[index].check = !newDoc[section].checkDocuments[index].check;
  setDocuments(newDoc)
}

Upvotes: -1

Related Questions