Step by step tutorial on making a To-Do list React application using the newly proposed Hooks
Hello there! Hooks is perhaps one of the most exciting new features about to hit the newest version of React this winter and I wanted to have a look at what the React community is raving about and get my feet wet following a few intro tutorials.
First things first, If you haven’t seen the React 2018 presentation video on Hooks please have a look at it. This guide will be here at your return and it will be worth your time.
Hooks are certainly powerful and definitely simplify code making it more readable and compact. I ran into a few issues while following these good tutorials:
which is why I created and shared this revised step by step guide for the rest of us to try out and hopefully have no issues aka “forced coffee breaks” 😉 while following it.
This guide assumes you are running your dev box on macOS however most of the tools mentioned here are usually cross platform or have specific OS versions available. Also a good understanding of the command line, JavaScript and React would be advisable. I included a link to documentation of many of the concepts and tools discussed here to help out as well.
You will need the following tools installed to follow this guide. These were the versions that I had when it all just worked with no issues, something higher or equal to these should be fine:
You can check which version you have of NPM, Yarn and Node by typing the following on a command line: tool-name --version
Installing these tools is easy using Homebrew on macOS. If you don’t have Homebrew you can find it here.
Once Homebrew is installed type: brew install node yarn
to install all three tools, if necessary.
Now we’re ready to create our React test app. At the command line, navigate to the directory where you want your React application to live. (If you’re new to the command line check out this introduction.)
Once you’re in the desired parent directory, type: npx create-react-app todo-app
to create the initial React app we’ll use as our starting point. If you’re on an older npm version without npx, follow the steps in the create-react-app installation guide. When it completes, type: cd todo-app
to dive into the newly created application directory.
Since Hooks is a feature only found on an alpha version of React (when this guide was written) we need to upgrade our newly created project with a newer React, to do so we enter: yarn add react@next react-dom@next
The “next” version identifier helps us get the alpha version of react which at the moment of this writing was 16.7.0-alpha.2.
Let’s make sure everything is set up as expected. Type: yarn start
and expect the command line to display instructions with the URL to see the app inside a web browser. If you grant permissions for React to control Chrome or your browser of choice then a window/tab displaying the initial React app with the React logo should be displayed for you. You can stop the React app at anytime by pressing the ctrl+c keys inside the terminal window.
It is possible that as we modify our code we will break the browser displayed view of our app more than a few times, this is normal, at the end of our tutorial we will have a working to-do list application and all should be looking good.
Using your favorite code editing program (mine being Microsoft’s free Visual Studio Code), open the file named App.js found inside the src directory.
Our first goal is to capture input from our user so a good old input field inside a form should make it happen for us. As of now your project directory tree should look similar to this:
node_modules/
package.json
public/
README.md
src/
App.css
App.js
App.test.js
index.css
index.js
logo.svg
serviceWorker.js
yarn.lock
Go ahead and create a new file inside the src directory called Form.jsx (“JSX” is a syntax extension to JavaScript which you can learn more about here.)
Enter the following JSX code in that file and save it:
import React, { useState } from 'react';
const useInputValue = initialValue => {
const [value, setValue] = useState(initialValue);
return {
value,
onChange: e => setValue(e.target.value),
resetValue: () => setValue(initialValue)
}
}
export default ({onSubmit}) => {
const {resetValue, ...text} = useInputValue("");
return (
<form onSubmit= {e =>
{
e.preventDefault()
onSubmit(text.value)
resetValue()
}
}>
<input {...text} placeholder='Enter new to-do and hit enter' />
</form>
)
}
Let’s look at what is happening in this file. To begin with, the file itself allows us to isolate our input field functionality from the rest of our app and exports a reusable component which can be imported and used by multiple views in our App.
Line 1 imports the React
package and selects the useState
hook from it.
Lines 2 through 9 define a custom hook we’ll use to manage the value of our input field. Our hook expects a single argument representing the initial value of the input field.
Line 3 leverages the built-in useState
hook to set up the state we’ll use to manage our input field’s value, providing the initialValue
argument as its initial value. Note the use of a destructuring assignment to get value
(the current state value) and a setValue
function we can use to update the state.
An important thing to notice is that every time we call the useState
hook we are creating a new state that gets assigned to whatever name we want to use for our const variable. We used value
but it can be anything. By Hooks convention, the setter function will always be prefixed with set
+ NameOfVar. So a variable like dog
will have a setDog
setter function to update its value. Using this pattern it is possible to have many individual states declared and used in a single component.
Lines 4 through 8 create and return an object containing the current value, an onChange
function to update the value, and a function to reset our input field value to the initial value once we’ve captured our entered to-do list item.
Lines 10 through 23 contain the meat of our Form
component, accepting a single onSubmit
prop that will be called when our user submits a to-do item.
Line 11 uses another destructuring assignment to invoke our custom hook. resetValue
is our function to reset the input field back to its initial value and our second variable is prefixed using spread syntax (the … three dots + variable). This way we can capture all the other remaining properties of our custom hook and provide them directly to our <input>
element.
Lines 13 through 22 return a JSX fragment containing a <form>
element with an onSubmit
event handler performing three things when the user triggers the submit event, typically by hitting the return key or clicking a submit button. The onSubmit
handler:
onSubmit
prop with the input field’s value.resetValue
function to clear up the field value once we are done using it.Line 20 defines an <input>
element using the spread syntax as described above to provide its value
and bind its onChange
event to our custom hook, ensuring the custom hook remains in sync with changes made to the <input>
element.
Modifying our App.js main file to use our Form component and manage our To-Do list
Open the App.js file and delete all code from it, we will replace it with the following code:
import React, { useState } from 'react';
import './App.css';
import Form from './Form';
export default () => {
const [todos, setTodos] = useState([]);
const toggleComplete = (index) => setTodos(
todos.map((todo, k) => k === index ? {...todo, complete: !todo.complete } : todo)
)
return (
<div className="App">
<Form
onSubmitCallback={(text) => {setTodos([{ text, complete: false}, ...todos])
}}/>
<div>
<div className="header">{(todos.length?'To-dos: (click to mark as done)':'')}</div>
{
todos.map(({ text, complete }, index) => {
return <div key={index} onClick={() => toggleComplete(index)}
style={{textDecoration: complete ? 'line-through':''}}>{text}</div>
}
)
}
</div>
<button onClick={() => setTodos([])}>reset list</button>
</div>
);
};
Great, now let’s have a look at what each line is doing:
Line 1 imports React
and gets the useState
hook from it.
Line 2 imports a CSS file which you can use to style the appearance of the to-do application elements.
Line 3 imports the Form
component we previously created.
Lines 5 through 28 define the App
component we will be exporting.
Line 6 leverages the built-in useState
hook to setup state for our list of to-do items providing an empty array as its initial value. No more calling setState
or this.state
!
Lines 7 through 9 define a toggleComplete
function we’ll use to toggle the completion status of our to-do items. It expects an index
value identifying the item that we want to toggle, creates a new array of to-dos with the appropriate one toggled (based on the index
), and calls the setTodos
function with that array to update our state.
Lines 10 through 27 returns a JSX fragment containing our main application view.
Lines 12 through 14 call our Form
component supplying a callback function for the onSubmit
prop so we can update our to-do list as new items are added. The callback function creates a new array consisting of the newly added item (defaulting to an “incomplete” state) and again uses the spread syntax to include all the pre-existing to-do items. That array is then provided to setTodos
to update our state.
Line 16 provides a text header and instructions if the to-dos list has more than zero items.
Line 18 uses map
to iterate over the captured to-do items, destructuring the text
and complete
values for each.
Lines 19 and 20 use those text
and complete
values to build a basic div element with three attributes: a key
, an onClick
handler that calls the toggleComplete
function defined earlier which adds a line-through style
when complete
is true. The text
value for the to-do item is passed and parsed between the opening and closing div tags.
Line 25 adds a <button>
element that will reset the to-do list when clicked. This simply calls the setTodos
function with an empty array as its argument, effectively clearing out our state.
If all goes as expected you should be able to navigate to http://localhost:3000 and see your app in action. If you stopped the server earlier, just type yarn start
at the command line to start it back up.
For a better spaced layout display of the app you can copy the following CSS into the App.css file, either replacing all of its contents or pasting it after the last line of CSS code found there. Save the file and reload.
.App form {
padding: 20px;
}
.App input {
width: 300px;
height: 30px;
}
.App button {
margin-top: 50px;
}
.App .header {
padding-bottom: 15px;
font-size: 14px;
color: grey;
}
Now try it out! Enter a to-do item into the field and hit the return key to add it to the list. The input field will reset so you can enter another to-do item. You can click on any item to mark or unmark it as compete.
Thank you for reading. Our part II of this tutorial will be adding a back end using GraphQL and MongoDB to persist your to-do items.
Happy coding!