npm install create-react-app --global
The new react app is created with
create-react-app react-todo
It’ll create a new folder react-todo
with some files and folders.
package.json
contains the list of modules that application uses. Each module performs a function or a set of functions.
node_modules
stores all packages that are listed in package.json.
src
contains all React source code files.
public
contains static files such as stylesheets and images.
Run this app with npm start
cd react-todonpm start
You’ll get a Welcome page…
As the instructions say, modify the App.js to make changes. App.js
is rendered from the index.js
file. Consider App.js
as the container for all other components.
Experience the ToDo app below. Enter the task in the input box, click on add button to add to the list. To remove from the list, click on the todo to be removed.
Go to the App.js
file in src
. Remove everything from the return except the parent div. It looks like below
import React, { Component } from 'react'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
</div>
)
}
}
export default App
All components will go inside the div in this return statement.
Todo list is form and a display below as you’ve experienced above.
We create the component TodoList
inside TodoList.js file in the src directory.
We import this is the App.js so that we can put this in the div we’re talking about.
Also, put the TodoList in the div in return statement.
What’s in the TodoList?
import React, { Component } from 'react'
class TodoList extends Component {
render() {
return (
<div className="todoListMain">
<div className="header">
<form>
<input placeholder="Task" />
<button type="submit"> Add Task </button>
</form>
</div>
</div>
)
}
}
export default TodoList
You might recognize some redundant divs, don’t worry we’re filling them in a while.
This component creates the form.
It looks like this…
Your output won’t same as mine because of the CSS. I secretly added the CSS to index.css
file. It’s basic stuff and we’re not discussing about stylesheet after this. If you want your app styled like in the example, grab the index.css from here…
If you try to add the todo in this app, it’ll just reload the page. This is because the default behavior of the form is to submit to the same page.
We already have a lifeless todo app, which does nothing other than displaying itself.
Here’s what we’ll be doing:
The input items are submitted when the form is submitted. To handle this operation, add the onSubmit to form tag in the TodoList.
This addItem
should be handled at the App component. It is passed to other components as a prop.
This must exist in App to pass. Create a addItem
property in the App
.
We could declare this as an old JavaScript (ES5) like function, but it won’t bind the form with it. We have to bind it manually through the constructor. I’d get rid of this using ES6 like syntax.
We still need the state to hold the array of items. The state makes it easy to render and elements on the page. All components using data will change automatically when the data in state changes.
We also need another state called currentItem
to hold the current value in the memory. It is an object and it also has key along with the text. React uses this key internally to only render the components when there are multiple similar components. The to-do list cannot be rendered without a key as there will be all li
s.
Add a constructor to the App
. Also, add addItem
and handleInput
to the same.
The addItem manages to add to the list, and handleInput manages the change in the input field.
This is what my App.js looks like…
import React, { Component } from 'react'
import './App.css'
import TodoList from './TodoList'
class App extends Component {
constructor() {
super()
this.state = {
items: [],
currentItem: {text:'', key:''},
}
}
handleInput = e => {
console.log('Hello Input')
}
addItem = () => {
console.log('Hello Add Item')
}
render() {
return (
<div className="App">
<TodoList addItem={this.addItem} />
</div>
)
}
}
export default App
To get the input element we must have a way to refer to it. You might be excited to use querySelector, but the React doesn’t like that. While it’s totally valid, the idea of virtual DOM is not to directly interact with the DOM but the components in the DOM.
To refer to the input, we create a ref
with inputElement =React.createRef()
. Pass this to TodoList
just like the addItem
inputElement = {this.inputElement}
Use it as ref = {this.props.inputElement}
in the TodoList
.
If you try the app at this moment, you can see it logs the message from addItem and then reloads. The reloading is the default behavior of the form submission.
To stop this behavior modify the addItem to the following.
addItem = e => {
e.preventDefault()
console.log('Hello World')
}
The preventDefault
will prevent reloading from submitting the form.
Here’s all the data we pass to TodoList…
<TodoList
addItem={this.addItem}
inputElement={this.inputElement}
handleInput={this.handleInput}
currentItem={this.state.currentItem}
/>
addItem
to handle click on add.
inputElement
to refer to this element.
handleInput
to handle data on input field on a change
currentItem
to display the value of the state set to currentItem.
Here’s what my TodoList.js looks like…
import React, { Component } from 'react'
class TodoList extends Component {
componentDidUpdate() {
this.props.inputElement.current.focus()
}
render() {
return (
<div className="todoListMain">
<div className="header">
<form onSubmit={this.props.addItem}>
<input
placeholder="Task"
ref={this.props.inputElement}
value={this.props.currentItem.text}
onChange={this.props.handleInput}
/>
<button type="submit"> Add Task </button>
</form>
</div>
</div>
)
}
}
export default TodoList
We’ll talk about eh ComponentDidUpdatein a while…
form
on submit, calls addItem
ref
refers to the current element.
value
is stored as text in the currentElement
object.
If you do not have onChange
in the component, the field will be read only. We do not want this.
onChange
calls handleInput and that’s next to discuss.
handleInput = e => {
const itemText = e.target.value
const currentItem = { text: itemText, key: Date.now() }
this.setState({
currentItem,
})
}
The handleInput gets the event, it gets the value from the input box and sets the state to and object of currentItem
. It has key as current data and text as the input data. The key is Date.now() which is the number of milliseconds from 1970 to now. It can only take a maximum of 1 input per millisecond. That’s enough for our case.
We need this object because we need to store this value to the array items
when user submits the form.
addItem = e => {
e.preventDefault()
const newItem = this.state.currentItem
if (newItem.text !== '') {
console.log(newItem)
const items = [...this.state.items, newItem]
this.setState({
items: items,
currentItem: { text: '', key: '' },
})
}
}
The addItem
prevents default reload. It gets the value in the input box from the state currentItem
.
Since we do not want to add empty value to our todo, we check for that. If it’s not empty, items array is destructured and newItem
is added.
We have to set this items[] to the state, we call this.setState
. It also makes sense to reset the currentItem
to clear the input box.
ComponentDidUpdateis one of the lifecycle methods in React. We’ve talked about all of them here. ComponentDidUpdate is called to focus on the input box referred by the inputElement
reference. The component is updated on submitting the form. this.props.inputElement.current.focus()
sets the focus in the input area so we can continue typing the next item in the todo list.
We have all the todos in the state, all we need is another component that can render these on the screen.
We’ll call this component TodoItems
and pass all items as a prop.
Here’s what TodoItems
looks like…
import React, { Component } from 'react'
class TodoItems extends Component {
createTasks(item) {
return <li key={item.key}>{item.text}</li>
}
render() {
const todoEntries = this.props.entries
const listItems = todoEntries.map(this.createTasks)
return <ul className="theList">{listItems}</ul>
}
}
export default TodoItems
The function createTasks returns li for each passed item. It uses the key we provided earlier. It will not work with a key at this stage because React must be able to differentiate between the multiple items to re-render the appropriate one.
All these list items are saved to listItems
using a mapping function. This gets used in the ul
in return statement.
We have added the ToDo, we probably want to remove some.
We already have the displaying todos in the TodoItems.js
, we make a small modification. Just add an onClick listener to deleteItem with the key.
createTasks = item => {
return (
<li key={item.key} onClick={() => this.props.deleteItem(item.key)}>
{item.text}
</li>
)
}
This executes deleteItem with the key as a parameter. The prop has to be passed from the App
.
<TodoItems entries={this.state.items}deleteItem={this.deleteItem}/>
Create a new property in App.js as deleteItem
.
deleteItem = key => {
const filteredItems = this.state.items.filter(item => {
return item.key !== key
})
this.setState({
items: filteredItems,
})
}
It filters the received key from the items
state. Then sets the items to filtered items.
☞ JavaScript for React Developers | Mosh
☞ React Tutorial - Learn React - React Crash Course [2019]
☞ Learn React - Full Course for Beginners - React Tutorial 2019
☞ React + TypeScript : Why and How