Next/React - How to correctly build up state from multiple child components

Viewed 36

I think this is easier to explain using a codesandbox link. This follows on from a previous question of mine, which may help provide more overall context. Currently, when interacting with the child elements (i.e. inputs), the state updates to {"values":{"0":{"Text1":"test"},"1":{"bool":true}}}. The issue is that if you interact with the other inputs within a Parent component, e.g. Text2 in the Parent component with id 0, it will overwrite the value already in the state, which makes it look like this {"values":{"0":{"Text2":"test"},"1":{"bool":true}}}. I want it to look like {"values":{"0":{"Text1":"test", "Text2":"test"},"1":{"bool":true}}}.

2 Answers

This is my try with your problem. I would like to have childIndex instead of number like you. It would be easier to work with other components later.

Here is my codesandbox

import { useEffect, useState } from "react"
import Parent from "./Parent"

const id1 = 0
const id2 = 1

interface Boo {
  childIndex: number
  value: {
    [name: string]: string | boolean
  }
}

const GrandParent: React.FC = () => {
  const [values, setValues] = useState<Boo[]>([])
  const valuesChange = (e: React.ChangeEvent<HTMLInputElement>, id: number) => {
    console.log("change event")
    const name = e.target.name
    let value: any
    if (name === "bool") {
      value = e.target.checked
    } else {
      value = e.target.value
    }

    setValues((prev) => {
      // Update new value to values state if input already there
      let updateBoo = prev.find((boo) => boo.childIndex === id)

      if (updateBoo) {
        // Update Values
        const valKeys = Object.keys(updateBoo.value)
        const valIndex = valKeys.find((val) => val === name)
        if (valIndex) {
          updateBoo.value[valIndex] = value
        } else {
          updateBoo.value = { ...updateBoo.value, [name]: value }
        }
      } else {
        // Create new if not added
        updateBoo = {
          childIndex: id,
          value: { [name]: value }
        }
      }

      return [...prev.filter((boo) => boo.childIndex !== id), updateBoo]
    })
  }

  useEffect(() => {
    console.log("Render", { values })
  })

  return (
    <>
      <div>{JSON.stringify({ values }, undefined, 4)}</div>
      <br />
      <br />
      <Parent valueChange={(e) => valuesChange(e, id1)} key={id1} />
      <Parent valueChange={(e) => valuesChange(e, id2)} key={id2} />
    </>
  )
}

export default GrandParent

The trick is you should return the previous state of the property too:

setValues((prev) => {
      return {
        ...prev,
        [id]: {
          ...(prev && (prev[id] as {})), // <= return the previous property state
          [name]: value
        }
      }
    })

I'm not very good at typescript but I tried my best to solve some types' errors

you can see an example below

Edit laughing-kowalevski-w1bhm1

Related