reactjslistinputfiltercontrolled-component

List of controlled components in React


I am trying to have a list of components where each one takes user input and have a separate state. I want then to be able to filter this list and get the components where for example "switch" is true. I have tried many things but it seems I am not able to figure out how to do this. Lastly I did this:

My parent component

export const Parking = () => {

const [position, setPosition] = useState([false,false,false])

const onBayClick = (index) => {
  
  position[index-1] === false? setPosition([...position.slice(0, index) , true, ...position.slice(index + 1)]):setPosition([...position, false])
}

const myBays = []
for (let i = 1; i < 4; i++) {
  myBays.push(<BayForm key = {i} child = {i}  state = {position[i]} onClick = {onBayClick}/>)
}
const [filteredBays, setFilteredBays] = useState(myBays)
const filterBays = () => {
  setFilteredBays(myBays.filter(bay => {
    return bay.position === false
  }))
}



return (
  <>
    
    {myBays}
   
    

  </>
)

}

My child component. I am only posting the relevant bits as it is too long.

 const handleClick = () => {
onClick(index)

}

 <FormGroup>
      <FormControlLabel control={<Switch checked = {state} onChange = {handleClick}  color = 'warning'/>} label="Switch"  />
</FormGroup>

But when I try to flick the switch, I get the following error:

"Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components%s"

What is the correct way of doing this? I get errors whatever I try.


Solution

  • Well, i suspect you indeed pass the undefined value from Parking down to the Switch right here:

    for (let i = 1; i < 4; i++) {
        myBays.push(<BayForm key = {i} child = {i}  state = {position[i]} onClick = {onBayClick}/>)
    }
    

    Your populated array position[i] has 3 values, hence valid indexes are 0, 1 and 2.

    So set it to:

    for (let i = 0; i < position.length; i++)
    

    If you need to render this index in your BayForm, just add 1 to it there


    Also see the problem here:

    const onBayClick = (index) => {
        position[index-1] === false
            ? setPosition([...position.slice(0, index) , true, ...position.slice(index + 1)])
            : setPosition([...position, false])
    }
    

    This line setPosition([...position, false]) means get existing array, copy it and combine it with false, which causes the array to grow every time. For changing one value in the array in immutable way i suggest to use map function:

    const onBayClick = (bayIndex) => {
        setPosition(previousPositions => previousPositions.map((it, index) => { // iterating through the array
        if (index === bayIndex) { // if the clicked index matches this one we iterate
            return !it // just set it's opposite value
        }
        return it // otherwise keep it as it is
        }))
    }