React is a front-end JavaScript library that can be used to create interactive user interfaces for your application. In this tutorial, you will create a to-do app that covers all four aspects of a CRUD: Create, Read, Update, and Delete.
This type of project is often done with Class components, but this application will instead integrate React Hooks. React Hooks allow for functional components to have a state and utilize lifecycle methods, allowing you to avoid using class components and have more modular and readable code.
Navigate to the place you would like your new application to be located and type:
npx create-react-app react-to-do
Note: Running npx
before the command allows it to be installed if it is not already installed globally on your machine.
Once it is done, navigate into the project:
cd react-to-do
Then run the project:
npm run start
Navigate to http://localhost:3000/
in your browser to see the spinning React logo.
At the time of this writing, React Hooks are in React 16.7, but React 16.7 is in alpha. You’ll need to install that specifically until it comes out of alpha:
npm install -S react@16.7.0-alpha.2 react-dom@16.7.0-alpha.2
Move into your src/App.css
file and add in the three classes we will be using throughout our app. Styling won’t be the focus of this tutorial, so we’ll keep this section short.
.app {
background: #209cee;
padding: 30px;
height: 100vh;
}
.todo-list {
background: #e8e8e8;
border-radius: 4px;
padding: 5px;
max-width: 400px;
}
.todo {
background: #fff;
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.15);
padding: 3px 10px;
font-size: 12px;
margin-bottom: 6px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: space-between;
}
With your application running and the styling ready to be used, let’s start on the Read part of CRUD. We’ll want to make a list of things so that we can Read/view the list.
Go into your src/App.js
file and add a state to our component. We are going to be using React Hooks, so state will look a little different than if we used classes.
function App() {
const [todos, setTodos] = useState([
{ text: "Learn about React" },
{ text: "Meet friend for lunch" },
{ text: "Build really cool todo app" }
]);
// we'll render our todos here ...
// return <div></div>
}
The component is a functional component. In past versions of React, functional components were unable to handle state, but now, by using Hooks, they can.
todos
, is what we are going to name our state.setTodos
, is what we are going to use to set the state.The hook of useState
is what React uses to hook into the state or lifecycle of the component. We then create an array of objects and we have the beginnings of our state.
Let’s take a moment to see how this would’ve been done with classes:
class App extends Component {
state = {
todos: [
{ text: "Learn about React" },
{ text: "Meet friend for lunch" },
{ text: "Build really cool todo app" }
]
}
setTodos = todos => this.setState({ todos });
render() {
return <div></div>
}
}
React Hooks allows for much cleaner code.
Todo
ComponentWe will want to create a component that we can use later on in the return of the main App component. We will call that Todo
and it will pass in the todo
and show the “text” part of the todo (todo.text
), like so:
const Todo = ({ todo }) => <div className="todo">{todo.text}</div>;
Let’s see how we will use that in our App
component.
Todo
Variable to Get a List ReturnedGo down to the return
part of the App
component and remove almost everything. We want to empty out that return part so that the spinning logo we saw earlier when we navigated to http://localhost:3000
goes away. Instead, our list will be displayed on the page.
By using the JavaScript method, map()
, we are able to create a new array of items by mapping over the todo
items from state and displaying them by index.
Let’s create a list of items:
return (
<div className="app">
<div className="todo-list">
{todos.map((todo, index) => (
<Todo
key={index}
index={index}
todo={todo}
/>
))}
</div>
</div>
);
Navigate to your browser where React is running. You will see something like this:
Now, let’s give our application the power to create a new item for our to-do app.
While in the src/App.js
file, we need to add a couple of things. At the top, we will add another component called TodoForm
. In this component we want to:
To set our state, we will write it like so:
const [value, setValue] = useState("");
The first is the “value” and the second is how we are going to be setting the state. The state starts off empty, and as we add things to our state, it will add it to our list of to-do items.
We will want to add in a handleSubmit
variable that can handle our addTodo
function (we will make that function soon) and add the item to the list. If nothing is in the input box and the user presses ENTER
, we want it to not do anything (that is, not add in an empty tile to the list).
Adding that functionality into a form that has an input box, we will have our code look like this:
[list src/App.js]
function TodoForm({ addTodo }) {
const [value, setValue] = useState("");
const handleSubmit = e => {
e.preventDefault();
if (!value) return;
addTodo(value);
setValue("");
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
className="input"
value={value}
onChange={e => setValue(e.target.value)}
/>
</form>
);
}
Let’s go ahead and build the addTodo
function now. Staying within App.js
, under the state of the App
component, the function will be able to grab the existing list of items, add on the new item, and display that new list.
const addTodo = text => {
const newTodos = [...todos, { text }];
setTodos(newTodos);
};
Notice that there is no this.state
. With the new React Hooks, we have no more need to use that, since the new Hooks understand that this.state
is going to be implied in certain places.
There is a spread operator in the code as well. The three dots before the todos
copy the list for us so that we are able to add on the new to-do item. Then using our keyword that we set earlier, we will set the state with setTodos
.
By using the TodoForm
down in the return of the App
component, we will see that input box pop up now. The entire src/App.js
file will look like this so far:
import React, { useState } from "react";
import "./App.css";
const Todo = ({ todo }) => <div className="todo">{todo.text}</div>;
function TodoForm({ addTodo }) {
const [value, setValue] = useState("");
const handleSubmit = e => {
e.preventDefault();
if (!value) return;
addTodo(value);
setValue("");
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
className="input"
value={value}
onChange={e => setValue(e.target.value)}
/>
</form>
);
}
function App() {
const [todos, setTodos] = useState([
{ text: "Learn about React" },
{ text: "Meet friend for lunch" },
{ text: "Build really cool todo app" }
]);
const addTodo = text => {
const newTodos = [...todos, { text }];
setTodos(newTodos);
};
return (
<div className="app">
<div className="todo-list">
{todos.map((todo, index) => (
<Todo
key={index}
index={index}
todo={todo}
/>
))}
<TodoForm addTodo={addTodo} />
</div>
</div>
);
}
export default App;
You can now add in a to-do item to your list in your browser.
Let’s add the functionality to cross off an item on our To-Do list.
Our state in our App
component needs a little extra for the “Completed” status to be able to change. We will be adding in another key/value pair to our list of objects. By adding in an “isCompleted: false” value, we will set that to false to begin with and will, when prompted, change that to true.
const [todos, setTodos] = useState([
{
text: "Learn about React",
isCompleted: false
},
{
text: "Meet friend for lunch",
isCompleted: false
},
{
text: "Build really cool todo app",
isCompleted: false
}
]);
We will need a function like the addTodo
function, but this one will be able to “complete” an item. We will want to do some similar things that we did in addTodo
, like using the spread operator to grab the current list of items. In this function, we will be changing the isCompleted
status to true
so that it knows it has been completed. It will then update the state and set the state to the newTodos
.
const completeTodo = index => {
const newTodos = [...todos];
newTodos[index].isCompleted = true;
setTodos(newTodos);
};
By using completeTodo
in the Todo
function, we are going to be able to use that functionality. When the Complete button is clicked, it will add in the textDecoration
styling and cross-out the item. We are using a ternary operator, a feature within ES6 JavaScript, which is another way of doing an if/else
statement. This is our way of completing an item on the list and “updating” the list. The code will look as follows:
function Todo({ todo, index, completeTodo }) {
return (
<div
className="todo"
style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
>
{todo.text}
<div>
<button onClick={() => completeTodo(index)}>Complete</button>
</div>
</div>
);
}
Go down to the return
of the App
component and add in the following line:
completeTodo={completeTodo}
The code will now look like this:
<div className="card-content">
{todos.map((todo, index) => (
<Todo
key={index}
index={index}
todo={todo}
completeTodo={completeTodo}
/>
))}
</div>
Returning to the browser, your to-do app will look like this when a “Complete” button is clicked.
Now we can read our list, add to our list, and update the completed status of each item. Next, we will add delete functionality.
By adding just a couple lines, we will be able to add in the functionality of deleting an item.
We will go ahead and build the removeTodo
function so that when we click on an “X” to delete an item, the item will be deleted. That function will be located by the others underneath the state of the App
component.
In this removeTodo
function, we will again be using the spread operator, but once we grab that current list, we will be splicing the chosen index off of the array of items. Once that is removed, we will return the new state by setting it with setTodos
to be newTodos
.
const removeTodo = index => {
const newTodos = [...todos];
newTodos.splice(index, 1);
setTodos(newTodos);
};
In your Todo
function, you will want to add in this line:
<button onClick={() => removeTodo(index)}>x</button>
This will look like the following:
function Todo({ todo, index, completeTodo, removeTodo }) {
return (
<div
className="todo"
style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
>
{todo.text}
<div>
<button onClick={() => completeTodo(index)}>Complete</button>
<button onClick={() => removeTodo(index)}>x</button>
</div>
</div>
);
}
You’ll also see that we are bringing in removeTodo
at the top and then using it in the onClick of the “X”.
Adding in removeTodo
in the Todo
part of returning the App
component, our “delete” will be fully functional. Add it in here:
<Todo
key={index}
index={index}
todo={todo}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
With that added in, go to your browser and you’ll see a button with an “X” that, when clicked, deletes the item completely.
The entire src/App.js
file will look like this in the end:
import React, { useState } from "react";
import "./App.css";
function Todo({ todo, index, completeTodo, removeTodo }) {
return (
<div
className="todo"
style={{ textDecoration: todo.isCompleted ? "line-through" : "" }}
>
{todo.text}
<div>
<button onClick={() => completeTodo(index)}>Complete</button>
<button onClick={() => removeTodo(index)}>x</button>
</div>
</div>
);
}
function TodoForm({ addTodo }) {
const [value, setValue] = useState("");
const handleSubmit = e => {
e.preventDefault();
if (!value) return;
addTodo(value);
setValue("");
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
className="input"
value={value}
onChange={e => setValue(e.target.value)}
/>
</form>
);
}
function App() {
const [todos, setTodos] = useState([
{
text: "Learn about React",
isCompleted: false
},
{
text: "Meet friend for lunch",
isCompleted: false
},
{
text: "Build really cool todo app",
isCompleted: false
}
]);
const addTodo = text => {
const newTodos = [...todos, { text }];
setTodos(newTodos);
};
const completeTodo = index => {
const newTodos = [...todos];
newTodos[index].isCompleted = true;
setTodos(newTodos);
};
const removeTodo = index => {
const newTodos = [...todos];
newTodos.splice(index, 1);
setTodos(newTodos);
};
return (
<div className="app">
<div className="todo-list">
{todos.map((todo, index) => (
<Todo
key={index}
index={index}
todo={todo}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
))}
<TodoForm addTodo={addTodo} />
</div>
</div>
);
}
export default App;
A to-do app can be a great reminder or starting point when it comes to CRUD in web development. Being able to read information, create new information, update existing information, and delete information can be powerful in any application.
In this tutorial, your created a CRUD To-do list app with React Hooks, which allowed for a more straight-forward way of coding and made the code clear and concise. If you’d like to learn more about React, check out the official React docs.
You can check out the completed project at the CodeSandbox for this project:
#reactjs #javascript #web-development