I have a react component called App that contains 2 components: Form and Table. And both of them are controlled component.
In the form, there is an input element and a button.
The input element has an onChange attribute; whenever the value changes it changes the value in the App's state.
The button has an onClick attribute that is provided by the App component; Whenever the button is clicked, the state's firstNames (which is an array) will be added with the state value of firstname.
The problem is when I clicked the button, it will throw an error saying that I didn't pass in an array and that it doesn't have a map method, even though in the call back, the updated state does show an array.
Below is the code:
function Form(props) {
return (
<form>
<label>
Item: <input value={props.value} onChange={props.handleChange} />
</label>
<button onClick={props.handleClick}>Submit</button>
</form>
);
}
function Table(props) {
let firstNames = props.names.map((item, index) => {
return (
<tr key={index}>
<td>{item}</td>
</tr>
);
});
return (
<table>
<thead>
<tr>
<th>First Name</th>
</tr>
</thead>
<tbody>{firstNames}</tbody>
</table>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputField: "",
firstNames: ["Joey", "Chloe"],
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(event) {
this.setState({ inputField: event.target.value });
}
handleClick() {
this.setState(
{
firstNames: this.state.firstNames.push(this.state.inputField),
},
console.log(this.state.firstNames)
);
}
render() {
return (
<div>
<Form
value={this.state.inputField}
handleChange={this.handleChange}
handleClick={this.handleClick}
/>
<Table names={this.state.firstNames} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
push
mutates the original array, but it returns the length
of the updated array, therefor, after the initial push, your firstNames
inside state will be a number, which doesn't have map
You shouldn't mutate state variables, you should create a new array instead when adding a new name, for example, like this:
this.setState({
firstNames: [...this.state.firstNames, this.state.inputField]
})
The full sample code would then look something like this:
function Form(props) {
return (
<form onSubmit={props.handleClick}>
<label>
Item: <input value={props.value} onChange={props.handleChange} />
</label>
<button>Submit</button>
</form>
);
}
function Table(props) {
let firstNames = props.names.map((item, index) => {
return (
<tr key={index}>
<td>{item}</td>
</tr>
);
});
return (
<table>
<thead>
<tr>
<th>First Name</th>
</tr>
</thead>
<tbody>{firstNames}</tbody>
</table>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputField: "",
firstNames: ["Joey", "Chloe"],
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(event) {
this.setState({ inputField: event.target.value });
}
handleClick( e ) {
event.preventDefault();
this.setState(
{
firstNames: [...this.state.firstNames, this.state.inputField],
inputField: ''
}, () => console.log(this.state.firstNames) );
}
render() {
return (
<div>
<Form
value={this.state.inputField}
handleChange={this.handleChange}
handleClick={this.handleClick}
/>
<Table names={this.state.firstNames} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Following 2 things I still updated in your code:
Added the type="button"
so that a submit doesn't happen
<button type="button" onClick={props.handleClick}>Submit</button>
Changed the callback of the setState
which you used for logging
this.setState({
firstNames: [...this.state.firstNames, this.state.inputField],
inputField: ''
}, () => console.log(this.state.firstNames) );
When you did it in your original way, the console.log
would be trigger before you could be sure that the setState
call has actually happened.
Another note perhaps could be that using index
for keys can lead to problems afterwards (be it sorting or removing items), a key should be unique. In this code, it's merely a hint