Up next is adding the event handler to update pendingItem as the user types in the input. This will go in between the constructor and the render method.
handleItemInput = e => {
this.setState({
pendingItem: e.target.value
});
}
Then, of course, we need to make this fire everytime the user types something in so we’ll add an event handler onto our input.
<input
className="input"
type="text"
onChange={this.handleItemInput}
value={this.state.pendingItem}
placeholder="Add an item"
/>
Now let’s back up a second and walk through this again. The ‘hanldeItemInput’ function is capturing the ‘onChange’ event and is updating the ‘pendingItem’ state every time a change occurs. This is happening through React’s built-in ‘setState’ method which automatically tells React to update the virtual DOM anytime it changes. This is how React knows to only update a specific part of the DOM instead of the whole thing. Pretty slick right?
You should be able to see the text rendering in the div under the input form as you type. Let’s take another peek at the React dev tools again. Watch the ‘pendingItem’ state change as you type into the input. ?
Now that we have our input capturing this awesomeness, it really should be its own separate component from App.js. You can go ahead and delete the div rendering the ‘pendingItem’ state we won’t need it going forward. Next, create a new file in the components folder and call it InputForm.js.
import React from "react";
const InputForm = props => {
return (
<form className="todoInput">
<input
className="input"
type="text"
onChange={props.handleItemInput}
value={props.pendingItem}
placeholder="Add an item"
/>
<button type="submit" name="submit" value="submit">
add
</button>
</form>
);
};
export default InputForm;
As you can see this is different from App.js. This is a stateless component which is a basic function that returns JSX. however, you’ll notice some differences. Instead of ‘state’, we’re using ‘props.’ Props are properties that are being passed into a component. These can than be accessed by calling ‘props.myProps’
Even though this is a different component we now have access to the event handler we created on App.js and the state, ‘pendingItem’ as well. Let’s pass these new props into our Input component in App.js and import the new component. Okay, we have made quite a few changes so let’s take a second to make sure we’re on the same page, now our App.js should look something like this.
import React, { Component } from "react";
import "../styles/reset.css";
import "../styles/App.css";
import InputForm from './InputForm';
class App extends Component {
constructor(props) {
super(props);
this.state = {
pendingItem: ""
};
}
handleItemInput = e => {
this.setState({
pendingItem: e.target.value
});
}
render() {
return (
<div className="wrapper">
<InputForm
className="input"
type="text"
handleItemInput={this.handleItemInput}
value={this.state.pendingItem}
placeholder="Add an item"
/>
</div>
);
}
}
export default App;
You’ll notice that on our InputForm component in App.js we changed onClick to handleItemInput. This is how props are passed down to children components. Now inside of InputForm.js, we have a handle to this method as props.handleItemInput.
Now on our form, we can type in items but we don’t really have a list yet. Let’s add some more state! Up in our constructor, we’ll add our list in the state.
state = {
list: [],
pendingItem: ""
};
Next, we need a handler to update the state everytime the user clicks the submit button. We’ll add that to App.js.
newItemSubmitHandler = e => {
e.preventDefault();
this.setState({
list: [
{
name: this.state.pendingItem,
},
...this.state.list
],
pendingItem: ""
});
};
Here we are preventing the submit button from refreshing the page. Then we capture the input name and save it to our list state, followed by adding back in any previous items in the list with the spread operator. Now we have to sync it up to the input form.
return (
<form
onSubmit={props.newItemSubmitHandler}
className="todoInput">
...
</form>
);
as well as make sure we pass through to props…
render() {
return (
<div className="wrapper">
<InputForm
className="input"
type="text"
handleItemInput={this.handleItemInput}
newItemSubmitHandler={this.newItemSubmitHandler}
value={this.state.pendingItem}
placeholder="Add an item"
/>
</div>
);
}
Make sure you double check the Chrome dev tools and make sure the list state is updating everytime you click submit. If all looks good, let’s add our ‘List’ component so we can render the items on our list. Add a new file to the components folder called List.js.
import React from "react";
const List = props => {
return (
<ul>
{props.list.map((item, index) => (
<ListItem
key={index}
item={item.name}
/>
))}
</ul>
);
};
export default List;
This is another stateless component and we’re using Javascript’s map method to iterate over it and render a list item for every todo item we have entered into our state. This is the true beauty of React. Anytime we change the state this map function will re-render any added or removed items from ‘list’ in our state. (*when using ‘map’ you should always add a unique key to each element being rendered.)
Before moving on let’s be sure to import List into App.js and create our ListItem component as well.
import List from './List';
...
render() {
return (
<div className="wrapper">
<InputForm
newItemSubmitHandler={this.newItemSubmitHandler}
handleItemInput={this.handleItemInput}
pendingItem={this.state.pendingItem}
/>
<List list={this.state.list} />
</div>
);
}
As you can see the List component contains another component called ListItem. Let’s go ahead and create that file and import accordingly.
ListItem Component:
import React from "react";
const ListItem = props => {
return (
<li>
{props.item}
</li>
);
};
export default ListItem;
and import into List component:
import ListItem from "./ListItem";
You’ve done it. You now have a to-do list created with React!