Reputation: 240
I am very stuck. I am building a react flash card app and have asked a couple questions regarding setting state when state is an array of objects. I have been playing around and researching different solutions and I appear to be extremely close, but I just can't seem to get this code working properly. Here is my component:
import React, { Component } from 'react'
export class TermForm extends Component {
state = {
allTerms: [
{
term: '',
def: ''
},
{
term: '',
def: ''
},
{
term: '',
def: ''
},
{
term: '',
def: ''
},
{
term: '',
def: ''
},
{
term: '',
def: ''
},
{
term: '',
def: ''
},
{
term: '',
def: ''
},
{
term: '',
def: ''
},
{
term: '',
def: ''
}
]
}
onChangeTerm = (event, index) => {
event.preventDefault();
this.setState({
allTerms : [
...this.state.allTerms.splice(0,index) ,
{
...this.state.allTerms[index],
term: event.target.value
},
...this.state.allTerms.splice(index+1) ,
]
})
}
onChangeDef = (e, index) => {
e.preventDefault();
this.setState({
allTerms : [
...this.state.allTerms.splice(0,index) ,
{
...this.state.allTerms[index],
def: e.target.value
},
...this.state.allTerms.splice(index+1)
]
})
}
render() {
return (
this.props.numberOfTerms.map((index) => (
<div key={index}>
<input type="text" placeholder="TERM" value={this.state.allTerms.term} onChange={(event) => this.onChangeTerm(event, index)}></input>
<input type="text" placeholder="DEFINITION" value={this.state.allTerms.def} onChange={(e) => this.onChangeDef(e, index)}></input>
</div>
))
)
}
}
export default TermForm
For context, the return statement in my render method is returning a dynamic form. Basically the rendered form can have anywhere from one to ten lines (each line consisting of an input field for what term the user wants to add, and another input field for the definition) based on how many terms the user wants to add. I have separate onChange events for the term and the definition. When I set state for the first term (so going through the onChangeTerm and onChangeDef functions) the state is set just fine. For any number of cards attempted to be created after the first one, however, the state gets all messed up. By viewing the state in chrome's react tools, I can see that after trying to create a second card (and any number of cards after the first) the term is set, but then when I go to type in a definition, it clears the term value. I honestly am very confused. I am thinking that the problem is in one or both of my onChange functions, but I just can't seem to figure out what the problem is. Any help sure would be appreciated.
Upvotes: 1
Views: 98
Reputation: 310
I propose to store the array in a parent component and use the TermForm
component to display each term+def individually. When you then update one term/def it sets the new state and also propagates the information to the parent: https://codesandbox.io/s/pensive-morning-95cmx
I also render the app state to display that it works ;) One could also improve the creation of the allTerms
array. e.g. populate it with a loop
// App.js
import React, { Component } from "react";
import "./styles.css";
import { TermForm } from "./TermForm";
export default class App extends Component {
state = {
allTerms: [
{
term: "",
def: ""
},
{
term: "",
def: ""
},
{
term: "",
def: ""
},
{
term: "",
def: ""
},
{
term: "",
def: ""
}
]
};
updateTermDef = (term, def, idx) => {
const newTerms = [...this.state.allTerms];
newTerms[idx] = {
term,
def
};
this.setState({ allTerms: newTerms });
};
render() {
return (
<div className="App">
{this.state.allTerms.map((termDef, idx) => (
<TermForm
term={termDef.term}
def={termDef.def}
idx={idx}
updateTermDef={(term, def) => this.updateTermDef(term, def, idx)}
/>
))}
<h1>App state: </h1>
{this.state.allTerms.map(termDef => (
<>
<span>
{termDef.term} : {termDef.def}
</span>
<br />
</>
))}
</div>
);
}
}
//TermForm.js
import React, { Component } from "react";
export class TermForm extends Component {
state = {
term: this.props.term,
def: this.props.def
};
render() {
return (
<div key={this.props.idx}>
<input
type="text"
placeholder="TERM"
value={this.state.term}
onChange={event =>
this.setState(
{ term: event.currentTarget.value },
this.props.updateTermDef(this.state.term, this.state.def)
)
}
/>
<input
type="text"
placeholder="DEFINITION"
value={this.state.def}
onChange={event =>
this.setState(
{ def: event.currentTarget.value },
this.props.updateTermDef(this.state.term, this.state.def)
)
}
/>
</div>
);
}
}
Upvotes: 2