kn0t
kn0t

Reputation: 343

Controlled Input onChange Event Fires Only Once - React

Only one key press is registered, then the input loses focus however I can click back into the component and add ... one character. State is updated.

Since the child components are state-less I assumed that just passing the handlers down as props as described in the docs would be sufficient, but everything from changing the binding for the method to adding a constructor have yielded the same results. I can usually find an answer on the site but no luck this time.

const list = [
  {
    id : 0,
    title : "Went well",
    showCard : false,
    cards : [],
    color : "pink"
   },
   {
    id : 1,
    title : "To Improve",
    showCard : false,
    cards : [],
    color : "yellow"
  },
  {
    id : 2,
    title : "Action Items",
    showCard : false,
    cards : [],
    color : "blue"
  }
]


class App extends Component {

  state = {list : list, usrInpt : ""}

  handleChange = e => {this.setState({usrInpt:e.target.value})}

  add = e => {
    e.preventDefault()
    let updatedList = this.state.list.map(obj => {
      if(obj.id == e.target.id) 
this.state.list[obj.id].cards.push(this.state.usrInpt)
      return obj
    })
    console.log("uL",updatedList)
    this.setState({list:updatedList}) 
    //this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
  }

  render() {
    return (
      <div className="App">
        <h2>Metro Board</h2>
        <ul className="container">
        {this.state.list.map((item) =>
            <List key={(Math.random() * 1)} text={item.title}
                    id={item.id} cards={item.cards} color={item.color} 
                    usrInpt={this.state.usrInpt} add={this.add} handleChange={this.handleChange}/>
        )}
        </ul>
      </div>
    )
  }
}

const List = (props) =>{

return(
<li>
    <h3>{props.text}</h3>
    <ul className="stack">
    <li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
    {props.cards.map((card,i)=> {
        console.log("card",card)
        return <ListItem key={(Math.random() * 1)}
                    idx={i}
                    color={props.color}
                    handleChange={props.handleChange}
                    usrInpt={props.usrInpt}/>
        })}
    </ul>
</li>
)

}

const ListItem = (props) =>{
    console.log("card props and value",props)
    return <li>
            <div className="card" style={{backgroundColor: props.color}}>
              <textarea type="text"
                    className="card"
                    placeholder="Enter text here"
                    value={props.usrInpt}
                    onChange={e => props.handleChange(e)}>
              </textarea>
          <div><a className="ltCtl" href="./logo" onClick={e => console.log(e)}>&lt;</a>
               <a className="clCtl" href="./logo" onClick={e => console.log(e)}>x</a>
               <a className="rtCtl" href="./logo" onClick={e => console.log(e)}>&gt;</a>
          </div>
        </div>
       </li>
  }

Both List && ListItem are separate files... Any help would be great. Thanks.

Upvotes: 1

Views: 3625

Answers (2)

kn0t
kn0t

Reputation: 343

UPDATE:

I was able to reach out to a full time developer and it seems I screwed up by trying to make unique keys :

The key needs to be consistent, but in this case it is a different value every time React uses the key when it IDs which element is focusing on, but in this case, it is different than the last render. So React does not know what to focus on. You can have unique keys if you use a string with the index of the loop in it, or if you use an ID that you store outside in the loop, like in state It does need to be unique, but it also needs to be consistent.

So the code:

      return (
        <Card
          key={Math.random() * 1} // <--- Don't!!
          idx={i}
          color={props.color}
          handleChange={props.handleChange}
          usrInpt={props.usrInpt}
        />
      );

was causing React to lose track of what to render since the keys where changing with each render. The preferred method is using a string interpolation with an identifier and an index if a loop is used:

return(
<li>
    <h3>{props.text}</h3>
    <ul className="stack">
    <li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
    {props.cards.map((card,i)=> {
        console.log("card",card)
        return <Card key={`card-column-${props.id}-${i}`}
                    idx={i}
                    color={props.color}
                    handleChange={props.handleChange}
                    usrInpt={props.usrInpt}/>
        })}
    </ul>
</li>
)

Which was also a comment made by @miyu ... which I did not test. Listen to your peers and mentors... you will not lose 12 hours chasing bugs. Thanks.

Upvotes: 2

Irene Mitchell
Irene Mitchell

Reputation: 44

state is non-hierarchical. Meaning, when you update a child object of your state but the parent object is not updated, then react will not trigger componentDidChange. Try adding a counter which gets updated when the list is updated.

add = e => {
    e.preventDefault()
    let updatedList = this.state.list.map(obj => {
      if(obj.id == e.target.id) 
this.state.list[obj.id].cards.push(this.state.usrInpt)
      return obj
    })
    console.log("uL",updatedList)
    let counter = this.state.counter || 0;
    this.setState({list:updatedList, counter: counter++}) 
    //this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
  }

Upvotes: 0

Related Questions