Update nested array in this.setState

Viewed 1820

I asked this question yesterday but upon reading it I noticed I worded it poorly, allow me to rephrase. What I'm trying to achieve is have one initial group (an array) and allow a user to create more groups (add arrays). And with each group they can upload images. And for each uploaded image I want to show a preview within it's respected group.

enter image description here

The image above is the initial group (an empty array). You can see this by my current state.

this.state = {
    data: {
        groups: [[]]
    },
    preview: [[]]
}

I map through groups to display the image above which consists of an input["file"] aswell as a div to the right that display's the photo preview. This code looks as such..

groups.map((element, index) => {
    return <div className="form-group" key={index}>
        <label htmlFor={"photos-" + index} className="form-group-label">Upload Photo</label>
        <input
            id={"photos-" + index}
            type="file"
            style={{display: 'none'}}
            onChange={(e, index) => this.handlePhotoChange(e, index)}
        />
        <div className="form-group-previews">
            {
                preview[index].map((image, id) => {
                    return <img key={id} src={image} alt={id} />
                })
            }
        </div>
    </div>
})

Where I'm having trouble is getting the file and image preview into the same index in the respected array (groups and preview). Right now my onChange event handler looks like this.

handlePhotoChange(e, index) {
    e.preventDefault()

    let reader = new FileReader()
    let file = e.target.files[0]

    const { data, preview } = this.state
    const { groups } = data

    reader.onloadend = () => {
        this.setState({
            data: {
                ...data,
                groups: [...groups, [...index, file]] // error
            },
            preview: [...preview, [...index, reader.result]] // error
        })
    }

    reader.readAsDataURL(file)
}

Is what I'm trying to do not possible with the spread operator in this.setState? What I want to achieve would seem to be done more like

groups[index]: [...groups[index], file]
preview[index]: [...preview[index], reader.result]

but something like that is a syntax error. Is there a better way to do this?

SOLUTION**

2 Answers

You will have to grab the item from the array based on the index, and then add the file to the item that was just grabbed.

index is just a number and you are applying a spread operator against it which is the reason for the error.

reader.onloadend = () => {
  // clone the array from state so that it is not mutated directly
  let clonedGroups = [...groups];
  let clonedPreview = [...preview];
  // grab the corresponding item based on the index
  let groupItem = clonedGroups[index] || [];
  let previewItem = clonedPreview[index] || [];

  // Update the corresponding item
  groupItem = [...groupItem, file];
  previewItem = [...previewItem, reader.result];

  // set the state with updated array objects
  this.setState({
    data: {
      ...data,
      groups: clonedGroups
    },
    preview: clonedPreview
  })
}

try updating the state this way:

 reader.onloadend = () => {
    this.setState({
        data: {
            ...data,
            groups: groups.map((groupItem, i)=> i===index?[...groupItem,...file] : groupItem )
        },
        preview:  preview.map((previewItem, i)=> i===index?[...previewItem,...reader.result] : previewItem )
    })
}
Related