Beginning React, 2024
Beginning React, 2024
Preface
Working Through This Book
Requirements
Contact
Chapter 1: Introduction
Computer Setup
Your First React Application
Explaining the Source Code
Summary
Chapter 2: Working With React Components
Returning Multiple Elements
Rendering to the Screen
Writing Comments in React
Composing Multiple Components as One
Summary
Chapter 3: Making Sense of JSX
Adding the Class Attribute
Summary
Chapter 4: Props and States
Passing Down Multiple Props
Props are Immutable
State in React
Passing State to a Child Component
Using React DevTools to inspect states and props
Summary
Chapter 5: CSS in React
React Inline Styling
CSS Files
CSS Modules
Tailwind CSS
Which One Should You Use?
Summary
Chapter 6: Project 1 - Wizard Form Application
Project Setup
Building the Wizard Form Structure
Summary
Chapter 7: Creating Wizard Form Mechanism
React Conditional Rendering
Partial rendering with a regular variable
Inline rendering with && operator
Inline rendering with conditional (ternary) operator
Hide Inactive Form Steps
Adding the Next Button
Adding the Previous Button
Summary
Chapter 8: Handling Events in React
Storing Form Input With onChange Event Handler
Wizard Form Submission
Summary
Chapter 9: Using Fetch in React
The useEffect Hook
Summary
Chapter 10: Project 2 - Creating a To-do List Application
Using Tailwind CSS
Laying the Application’s Foundation
Summary
Chapter 11: Updating States and Separating the Components
Creating the AddTask Component
Creating the TaskList Component
Updating Task Status
Deleting a Task
Editing a Task
Creating the TaskInput Component
Summary
Chapter 12: The Context API
Adding the Context API
Moving State Functions to the Context API
Consuming the Context API
Updating the TaskList Component
Update Components Reference in App Component
Summary
Chapter 13: Adding HTTP Requests to Persist Data
Creating the Server and Database
Fetching Data from json-server
Updating addTask() function
Updating updateTaskStatus() Function
Updating editTask() Function
Updating deleteTask() Function
Summary
Chapter 14: Adding Filter Function
Creating the TaskFilter Component
Summary
Wrapping Up
The Next Step
About the author
Beginning React
A Step-By-Step Gentle Guide to Learn React for Beginners
By Nathan Sebhastian
PREFACE
I encourage you to write the code you see in this book and run
them so that you have a sense of what web development with
React looks like. You learn best when you code along with
examples in this book.
Requirements
To experience the full benefit of this book, basic knowledge of
HTML, CSS, and JavaScript is required. No previous knowledge
about React is needed as we will start from the very basics.
Contact
If you need help, you can contact me at
nathan@codewithnathan.com and let me know what obstacles
you are having.
It’s minimalist. React takes care of only ONE thing: the user
interface and how it changes according to the data you feed
into it. React makes your interface dynamic with minimal code.
It has a low learning curve. The core concepts of React are
easy to learn, and you don’t need months or 40 hours of video
lectures to learn about them.
The bottom line is that with a low learning curve, React gives
you incredible power in making your UI flexible, reusable, and
spaghetti-free.
Computer Setup
To start programming with React, you need to have three
things:
1. A web browser
2. A code editor
3. Node.js
We’re going to use the Chrome browser to run our JavaScript
code, so if you don’t have one, you can download it here:
https://www.google.com/chrome/
https://code.visualstudio.com/
Now that you have a code editor installed, the next step is to
install Node.js
Installing Node.js
Node.js is a JavaScript runtime application that enables you to
run JavaScript outside of the browser. We need to install this
application on our computer to install packages required in
React development.
node -v
The next step is to open your terminal and run the npm
command to create a new React application using Vite.
cd my-react-app
npm install
npm run dev
When you’re done, follow the next steps you see in the output
above, use cd command to change the working directory to the
application we’ve just created, then run npm install to install
the packages required by the application.
Then, we need to run the npm run dev command to start our
application:
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
Now you can view the running application from the browser, at
the designated localhost address:
This means you have successfully created your first React app.
Congratulations!
Here, you should see several folders and files that make up the
React application as follows:
The vite.config.js is a configuration file that instructs Vite on
how to run the application. Because we have a React
application, you’ll see the React plugin imported inside:
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
When you run the npm run dev command, Vite will look into this
file to know how to run the program.
The package.json file stores the information about the project,
including the packages required to run the project without any
issues. The package-lock.json file keeps track of the installed
package versions.
src
├── App.css
├── App.jsx
├── assets
│ └── react.svg
├── index.css
└── main.jsx
First, the App.css file contains the CSS rules applied to the
App.jsx file, which is the main React application code.
The assets/ folder contains the assets required for this project.
In this case, it’s the React icon, which you had seen in the
browser.
The index.css file is the root CSS file applied globally to the
application, and the main.jsx file is the root file that access the
index.html file to render the React application. Here’s the
content of main.jsx file:
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
Here, you can see that the ReactDOM library creates a root at
the <div> element that contains the root ID, then renders the
whole React application to that element.
You can open the App.jsx file to view the React code:
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
}
function App() {
return <h1>Hello World</h1>
}
Next, delete the index.css, app.css, and assets/ folder. You also
need to delete the import './index.css' statement in your
main.jsx file.
If you open the browser again, you should see a single heading
rendered as follows:
Alright! Now we’re ready to learn the fundamentals of React.
Let’s start our first lesson in the next chapter.
Summary
In this chapter, we’ve explored the origins of React, and why
developers love to use it.
function App() {
return <h1>Hello World</h1>
}
function App() {
return null
}
All React components are saved under the .jsx file extension, as
you can see in this project that you have main.jsx and App.jsx.
function App() {
return (
<div>
<h1>Hello World!</h1>
<h2>Learning to code with React</h2>
</div>
)
}
But this will make your application render one extra <div>
element in the browser. To avoid cluttering your application,
you can render an empty tag <> like this:
function App() {
return (
<>
<h1>Hello World!</h1>
<h2>Learning to code with React</h2>
</>
)
}
You need to have an HTML file as the source from which your
React component is rendered. Usually, a very basic HTML
document with a <div> is enough, as you can see in the
index.html file:
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
Next, you render the component into the <div> element. Notice
how ReactDOM is imported from react-dom package, and the
document.getElementById('root') is used to select the <div>
element below:
function App() {
return <h1>Hello World!</h1>
}
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
Here, you can see that the App component is placed in the same
file as the ReactDOM library. This can be done if you want to
remove the App.jsx file, so you have only a single main.jsx file
as the source for your React application.
function App() {
return (
<>
<h1>Hello World!</h1>
<h2>Learning to code with React</h2>
</>
)
}
function App() {
return (
<>
<h1>Hello World!</h1>
{/* <h2>Learning to code with React</h2> */}
</>
)
}
function UserComponent() {
return <h1> User Component </h1>;
}
function ProfileComponent() {
return <h1> Profile Component </h1>;
}
function FeedComponent() {
return <h1> Feed Component</h1>;
}
<UserComponent>
▪
<ProfileComponent>
▪
<FeedComponent>
▪
The tree would then be rendered into the DOM through the
ReactDOM.render() method:
By composing multiple components, you can split the user
interface into independent, reusable pieces, and develop each
piece in isolation.
Summary
In this chapter, you’ve learned what are React components and
how you can use them to render HTML elements to the screen.
You’ve also seen an example of building a more complex user
interface by composing multiple components as one.
In the next chapter, you’re going to learn more about JSX, the
templating language created for React.
CHAPTER 3: MAKING SENSE OF
JSX
function App() {
return <h1>Hello World</h1>
}
The tag <h1> looks like a regular HTML tag, but it’s actually a
special template language included in React called JSX.
function App() {
const myElement = <h1>Hello World</h1>
return myElement
}
For example, let’s say you have an array of users that you’d like
to show:
const users = [
{ id: 1, name: 'Nathan', role: 'Web Developer' },
{ id: 2, name: 'John', role: 'Web Designer' },
{ id: 3, name: 'Jane', role: 'Team Leader' },
]
You can use the map() function to loop over the array:
function App() {
const users = [
{ id: 1, name: 'Nathan', role: 'Web Developer' },
{ id: 2, name: 'John', role: 'Web Designer' },
{ id: 3, name: 'Jane', role: 'Team Leader' },
]
return (
<>
<p>The currently active users list:</p>
<ul>
{
users.map(function(user){
// returns Nathan, then John, then Jane
return (
<li> {user.name} as the {user.role} </li>
)
})
}
</ul>
</>
)
}
Inside React, you don’t need to store the return value of the
map() function in a variable. The example above will return a
<li> element for each array value into the component.
When you don’t have any unique identifiers for your list, you
can use the array index value as the last resort:
users.map(function(user, index){
return (
<li key={index}>
{user.name} as the {user.role}
</li>
)
})
function App() {
return <h1 className='text-lowercase'>Hello World!</h1>
}
Summary
In this chapter, you’ve learned that JSX enables you to write
plain JavaScript code to serve as the template for your React
application.
JSX is plain JavaScript, so you can use all valid JavaScript syntax
inside a component.
Props and states are used to pass data inside React components.
Props (or properties) are inputs passed down from a parent
component to its child component. On the other hand, states are
variables defined and managed inside the components.
function ParentComponent() {
return <ChildComponent />
}
In the code below, the name prop with the value 'John' is passed
to the ChildComponent:
function ParentComponent() {
return <ChildComponent name='John' />
}
function ChildComponent(props){
return <p>Hello World! my name is {props.name}</p>
}
The props parameter will always be an object, and any prop you
define when rendering the component will be passed as a
property to the object.
function ParentComponent() {
return (
<ChildComponent
name="John"
age={29}
hobbies={["read books", "drink coffee"]}
occupation="Software Engineer"
/>
)
}
function ParentComponent() {
function greetings() {
return 'Hello World'
}
return <ChildComponent greetings={greetings} />
}
function ChildComponent(props) {
return <p>{props.greetings()}</p>
}
function ChildComponent(props){
props.name = 'Mark';
return <p>Hello World! my name is {props.name}</p>
}
function ParentComponent() {
return <ChildComponent name='John'/>
}
As you can see, React props can’t be changed once you declare
them. But what if your data needs to change as a user interacts
with your application? This is where state comes to the rescue.
State in React
In React, states are arbitrary data that you can declare and
manage in your components. To create a state in React, you
need to call the useState hook as shown below:
function ParentComponent() {
const [name, setName] = useState('John')
In React, hooks are functions that allow you to tap into the
features provided by React. The useState hook is a function that
enables you to put value into the state mechanism.
The first element holds the state value, and the second element
is a function that allows you to change the state value. You need
to use the destructuring array syntax to receive both elements
as shown above
You can give any names to the variables returned by useState,
but it’s recommended to use [something, setSomething]
To render the state value, you can embed it into JSX as follows:
function ParentComponent() {
const [name, setName] = useState('John')
If you want to change the value of the name variable, you need to
use the provided setName() function.
Instead, you can create a button that will change the value of
name when you click it:
function ParentComponent() {
const [name, setName] = useState('John')
return (
<>
<h1>Hello {name}</h1>
<button onClick={() => setName('Mark')}>Change Name</button>
</>
)
}
function ParentComponent() {
const [name, setName] = useState('John')
function ChildComponent(props) {
return (
<>
<h1>Hello {props.name}</h1>
<button onClick={() => props.setName('Mark')}>Change Name</button>
</>
)
}
Once installed, open the developer tool and you should have
two extra tabs called Components and Profiler as shown below:
Below, you can see the props and state of the ParentComponent, as
well as other details:
When you click on the button, the state value will change
accordingly. You can inspect the ChildComponent to view its
details. This DevTool will come in handy when you develop
React applications.
Summary
In this chapter, you’ve learned about props and states in React.
Both props and states allow you to create arbitrary variables
and pass data between React components.
1. Inline styling
2. CSS files
3. CSS modules
4. Tailwind CSS
The only difference with JSX is that inline styles must be written
as an object instead of a string. See the example below:
function App() {
return (
<h1 style={{ color: 'red' }}>Hello World</h1>
);
}
In the style attribute above, the first set of curly brackets is used
to write JavaScript expressions. The second set of curly brackets
initialize a JavaScript object.
Style property names that have more than one word are
written in camelCase instead of using the traditional
hyphenated style. For example, the usual text-align property is
written as textAlign in JSX:
function App() {
return (
<h1 style={{ textAlign: 'center' }}>Hello World</h1>
);
}
const pStyle = {
fontSize: '16px',
color: 'blue'
}
This will let you add inline styles to your already declared style
object. See the <p> element below:
const pStyle = {
fontSize: '16px',
color: 'blue'
}
JSX inline styles allow you to write CSS directly into your
component.
One of the benefits of using the inline style approach is that you
will have a simple component-focused styling technique. When
using an object for styling, you can extend your style by
spreading the object.
You can’t specify pseudo classes using inline styles. That means
you can’t define rules like :hover, :focus, :active, and so on.
/* style.css */
.paragraph-text {
font-size: 16px;
color: #ff0000;
}
Now, let’s import the CSS file into the App.jsx file and add the
class prop to the component:
import './style.css';
function App() {
return (
<p className="paragraph-text">
The weather is sunny today.
</p>
);
}
This way, the CSS will be separated from your JavaScript files,
and you can just write CSS syntax just as usual.
CSS Modules
A CSS module is a regular CSS file with all of its class and
animation names scoped locally by default.
When applying this method, each React component will have its
own CSS file, and you need to import that CSS file into your
component.
To let React know you’re using CSS modules, name your CSS file
using the [name].module.css convention.
Here’s an example:
/* App.module.css */
.BlueParagraph {
color: blue;
text-align: left;
}
.GreenParagraph {
color: green;
text-align: right;
}
function App() {
return (
<>
<p className={styles.BlueParagraph}>
The weather is sunny today.
</p>
<p className={styles.GreenParagraph}>
Still, don't forget to bring your umbrella!
</p>
</>
)
}
When you build your app, Vite will automatically look for CSS
files that have the .module.css name and process the class
names to a new localized name.
Using CSS Modules ensures that your CSS classes are scoped
locally, preventing CSS rules from colliding with each other.
.MediumParagraph {
font-size: 20px;
}
.BlueParagraph {
composes: MediumParagraph;
color: blue;
text-align: left;
}
.GreenParagraph {
composes: MediumParagraph;
color: green;
text-align: right;
}
Tailwind CSS
Tailwind CSS is a modern utility-first CSS framework that
allows you to style elements by combining a bunch of classes
together.
Summary
In this chapter, you’ve seen the four common methods that you
can use to apply CSS in React.
By clicking the next button, you can jump into the other parts of
the form. When you click on the submit button, an alert will be
triggered and your inputs will be displayed:
Figure 3. Wizard Form Alert
To keep things simple, the form will only have one input for
each step and you don’t need to validate the data.
Given the demo above, you basically need to break the form
into one parent component and three child components. The
parent component will show the correct form step based on
user clicks.
function App() {
const [currentStep, setCurrentStep] = useState(1);
const [email, setEmail] = useState('');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
}
function App() {
// ...
return (
<div className='container'>
<h1>React Wizard Form ♂</h1>
<p>Step {currentStep} </p>
<form>
<Step1 />
<Step2 />
<Step3 />
</form>
</div>
);
}
The component has a <form> element that wraps the three child
components.
Below the App() function, write the Step1, Step2, and Step3 child
components as shown below:
function Step1() {
return <h1>Step 1 Component</h1>;
}
function Step2() {
return <h1>Step 2 Component</h1>;
}
function Step3() {
return <h1>Step 3 Component</h1>;
}
Now let’s fill these step components with the form inputs:
function Step1() {
return (
<div className='form-group'>
<label htmlFor='email'>Email address</label>
<input
className='form-control'
id='email'
name='email'
type='text'
placeholder='Enter email'
/>
</div>
);
}
function Step2() {
return (
<div className='form-group'>
<label htmlFor='username'>Username</label>
<input
className='form-control'
id='username'
name='username'
type='text'
placeholder='Enter username'
/>
</div>
);
}
function Step3() {
return (
<div className='form-group'>
<label htmlFor='password'>Password</label>
<input
className='form-control'
id='password'
name='password'
type='password'
placeholder='Enter password'
value={props.password}
onChange={props.handleChange}
/>
</div>
);
}
Now if you run the application on the browser, you can see the
skeleton HTML structure finished:
It’s time to add some CSS to beautify this application. Open the
index.css created by Vite and replace the style rules there with
the following one:
body {
margin: 1rem;
font-family: sans-serif;
background-color: #edceb0;
}
.container {
margin-left: auto;
margin-right: auto;
background: #fff;
height: 424px;
width: 338px;
padding: 71px 93px 0;
border-radius: 10px;
box-shadow: 0 2px 7px 0 rgba(0, 0, 0, 0.1);
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-control {
width: 100%;
padding: 0.375rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
color: #495057;
border: 1px solid #ced4da;
border-radius: 0.25rem;
box-sizing: border-box;
}
The body style is used to add space, font family, and background
to the application. The .container style is used to beautify the
div containing the form. The label, .form-group, and .form-
control is used to make the form look good.
By the way, don’t forget to delete the App.css file and assets/
folder because we don’t need them.
Summary
In this chapter, we laid the foundations for our Wizard Form
Application by creating the components and styling them.
If you need to check your work, you can get the source code for
this chapter at https://github.com/nathansebhastian/react-
wizard-form-1
function App(props) {
const { user } = props
if (user) {
return <button>Logout</button>
}
return <button>Login</button>
}
export default App
In the example above, React will render the logout button when
the user value is truthy, and the login button when user is falsy.
function App(props) {
const { user } = props
if (user) {
button = <button>Logout</button>
}
return (
<>
<h1>Hello there!</h1>
{button}
</>
)
}
Instead of writing two return statements, you store the dynamic
UI element inside a variable and use that variable in the return
statement.
This way, you can have a component that has static and
dynamic elements.
function App() {
const newEmails = 2
return (
<>
<h1>Hello there!</h1>
{newEmails > 0 &&
<h2>
You have {newEmails} new emails in your inbox.
</h2>
}
</>
)
}
In this example, the <h2> element only gets rendered when the
newEmails count is greater than 0.
function App(props) {
const { user } = props
return (
<>
<h1>Hello there!</h1>
{ user? <button>Logout</button> : <button>Login</button> }
</>
)
}
function App() {
// ...
<form>
<Step1 currentStep={currentStep} />
<Step2 currentStep={currentStep} />
<Step3 currentStep={currentStep} />
</form>
}
function Step1(props) {
if (props.currentStep !== 1) {
return null;
}
return (
// ...
);
}
function Step2(props) {
if (props.currentStep !== 2) {
return null;
}
return (
// ...
);
}
function Step3(props) {
if (props.currentStep !== 3) {
return null;
}
return (
// ...
);
}
Once you save the changes, you should only see the first step on
the screen.
Now you need to add buttons so that the user can move from
one step to the other.
<form>
<Step1 currentStep={currentStep} />
<Step2 currentStep={currentStep} />
<Step3 currentStep={currentStep} />
<button
className='btn btn-next f-right'
type='button'
onClick={() => setCurrentStep(prevStep => prevStep + 1)}
>
Next
</button>
</form>
You can try the button on the browser, but notice that if you
click Next after the third step, the form will be empty.
function App() {
// useState...
So far so good. The next step is to add the Previous button so the
user can go back to the previous step.
Now call this function in the App component, just below the
nextButton() call:
<Step1 currentStep={currentStep} />
<Step2 currentStep={currentStep} />
<Step3 currentStep={currentStep} />
{nextButton()}
{previousButton()} // Render the Previous button
And that’s it. If you test the application now, you can move
between the form steps, and the button is rendered only when
the condition is right.
.f-right {
float: right !important;
}
.btn {
color: #fff;
cursor: pointer;
font-size: 1rem;
line-height: 1.5;
border-radius: 0.25rem;
padding: 0.375rem 0.75rem;
border: 1px solid transparent;
}
.btn-next {
border-color: #007bff;
background-color: #007bff;
}
.btn-previous {
border-color: #6c757d;
background-color: #6c757d;
}
The .f-right style adds the float property to the element to
move it to the right, the .btn is used to style the buttons.
Summary
In this chapter, you’ve seen how you can conditionally render
components based on the state values. By creating a state and
passing it to the child components, you can determine whether
the component should render something to the screen.
This is one of the reasons developers love using React. You write
code in a declarative way, specifying the conditions for change
in your application.
If you need the source code for this chapter, you can get it at
https://github.com/nathansebhastian/react-wizard-form-2
function App() {
const handleClick = (event) => {
console.log("Hello World!");
console.log(event);
}
return (
<button onClick={handleClick}>
Click me
</button>
)
}
When you click on the button above, the event variable will be
logged as a SyntheticBaseEvent object in your console:
Figure 4. React’s SyntheticBaseEvent Log
The use case for this Synthetic event is the same as the native
DOM event. You can respond to user interactions like clicking,
hovering, focusing or typing on a form input, submitting a
form, and so on.
We’ve seen how to handle user clicks before. Now we will see
how to respond to user inputs and form submissions.
The function will receive the event parameter, and will update
the right state based on the input’s name attribute.
function App() {
// ...useState
<form>
<Step1
currentStep={currentStep}
handleChange={handleChange}
email={email}
/>
<Step2
currentStep={currentStep}
handleChange={handleChange}
username={username}
/>
<Step3
currentStep={currentStep}
handleChange={handleChange}
password={password}
/>
{previousButton()}
{nextButton()}
</form>
function Step1(props) {
if (props.currentStep !== 1) {
return null;
}
return (
<div className='form-group'>
<label htmlFor='email'>Email address</label>
<input
className='form-control'
id='email'
name='email'
type='text'
placeholder='Enter email'
value={props.email} // set value as email state
onChange={props.handleChange} // the handleChange()
/>
</div>
);
}
You need to add the same props to Step2 and Step3 as follows:
// Step2
<div className='form-group'>
<label htmlFor='username'>Username</label>
<input
className='form-control'
id='username'
name='username'
type='text'
placeholder='Enter username'
value={props.username}
onChange={props.handleChange}
/>
</div>
// Step3
<div className='form-group'>
<label htmlFor='password'>Password</label>
<input
className='form-control'
id='password'
name='password'
type='password'
placeholder='Enter password'
value={props.password}
onChange={props.handleChange}
/>
</div>
<form onSubmit={handleSubmit}>
</form>
We only want the user to be able to submit the form in the third
step of the form, so let’s add a submit button to Step3 as follows:
function Step3(props) {
// ...
return (
<>
<div className='form-group'>
{ /* ...Label and Input */}
</div>
<button className='btn btn-submit f-right'>Sign up</button>
</>
);
}
Now the form is finished. You can test the application in the
browser.
But notice that the form inputs are still there after you submit
the data. To reset the form, let’s add several function calls to
reset the state as follows:
Summary
Congratulations on finishing your first React project! The
Wizard Form application helps you see how to build a web
application using React.
Always begin a project by finding out what the result looks like,
and then make your way step by step, adding code one block at
a time.
In the next chapter, we’re going to learn how to send and
receive data from a backend service in React. See you there.
CHAPTER 9: USING FETCH IN
REACT
React doesn’t tell you how you should send network requests.
The library only focuses on rendering UI with data
management using props and states.
To fetch data using React, you can use any valid JavaScript
library like Axios, Superagent, and even the native Fetch API.
When the hook has finished performing the data request, you
can set the response into your component states and render the
appropriate components based on the state values.
function App() {
const [title, setTitle] = useState('');
useEffect(() => {
getData();
}, []);
return <h1>{title}</h1>;
}
Here, you can see that the console.log() is called twice. This is
because the <React.StrictMode> wrapper always runs a
useEffect hook twice to help you in development.
When you want to run useEffect only once, you can pass an
empty array as the second argument to the function, as shown
in the example above.
Summary
In this chapter, you’ve seen how a React application can send
network requests using the native Fetch API.
React doesn’t tell you what library to use to fetch data. Instead,
it only exposes a hook that enables you to synchronize your
component to external systems.
In the next project, we’re going to see how to use the useEffect
hook when building an application.
CHAPTER 10: PROJECT 2 -
CREATING A TO-DO LIST
APPLICATION
Open the command line inside the todo-list folder, and run the
following commands:
/* index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
You are now ready to use Tailwind in your application. Open
the App.jsx file and create a simple heading component to test
this:
Run the application with npm run dev and you should see the
heading element styled with Tailwind styles:
Now we can import the icons to the App.jsx file like this:
This task state will serve as the dummy data for our tasks until
we connect the backend server with our application.
Inside the App component, write a return statement and add the
task title part as follows:
return (
{/* Task Title */}
<div className='max-w-lg px-5 m-auto mt-20'>
<h1 className='flex justify-center text-3xl font-bold underline'>
My Todo List
<FaCheckSquare style={{ color: '#42C239' }} />
</h1>
</div>
)
The next step is to add the form so that users can enter their
tasks:
return (
<>
{/* Task Title */}
{/* ... */}
{/* Add Task */}
<form>
<div className='flex items-center w-full max-w-lg gap-2 p-5 m-auto'>
<input
type='text'
placeholder='start typing ...'
className='w-full px-5 py-2 bg-transparent border-2 outline-none
border-zinc-200 rounded-xl placeholder:text-zinc-500 focus:border-zinc-700'
/>
<button
type='submit'
className='px-5 py-2 text-white bg-blue-500 border-2 border-
transparent rounded-lg hover:bg-blue-700'
>
Submit
</button>
</div>
</form>
</>
);
There’s nothing special with the elements above, except that we
add the wrapper <> .. </> to wrap the two components we
have. The class name is long because we’re using Tailwind CSS.
return (
<>
{/* Task Title */}
{/* ... */}
{/* Add Task */}
{/* ... */}
{/* Task List */}
<ul className='grid max-w-lg gap-2 px-5 m-auto'>
{tasks.map(task => (
<li
key={task.id}
className={`p-5 rounded-xl bg-zinc-200 ${
task.completed? 'bg-opacity-50 text-zinc-500':''
}`}
>
<div className='flex flex-col gap-5'>
<span
style={{
textDecoration: task.completed ? 'line-through' : 'none',
}}
>
{task.title}
</span>
<div className='flex justify-between gap-5'>
<button>
{task.completed ? (
<span className='flex items-center gap-1 hover:underline'>
<TbRefresh />
Set Active
</span>
) : (
<span className='flex items-center gap-1 hover:underline'>
<BsCheck2Square />
Set Completed
</span>
)}
</button>
<div className='flex items-center gap-2'>
<button
className='flex items-center gap-1 hover:underline'
>
<FaRegEdit />
Edit
</button>
<button
className='flex items-center gap-1 text-red-500
hover:underline'
>
<RiDeleteBin7Line />
Delete
</button>
</div>
</div>
</div>
</li>
))}
</ul>
</>
);
<li
key={task.id}
className={`p-5 rounded-xl bg-zinc-200 ${
task.completed? 'bg-opacity-50 text-zinc-500':''
}`}
>
</li>
One to cross out the task title when the task is completed:
<span
style={{
textDecoration: task.completed ? 'line-through' : 'none',
}}
>
{task.title}
</span>
And one for setting the button text between 'Set Active' and 'Set
Completed'
<button>
{task.completed ? (
<span className='flex items-center gap-1 hover:underline'>
<TbRefresh />
Set Active
</span>
) : (
<span className='flex items-center gap-1 hover:underline'>
<BsCheck2Square />
Set Completed
</span>
)}
</button>
You can save the changes and run the application to see that we
have the application foundation done.
Summary
You have put together the foundation for the To-do List
application. If you encounter any error, you can check the
source code used in this chapter at
https://github.com/nathansebhastian/react-todo-1
Now that we have a dummy state for the tasks data, the next
step is to add functionalities to update the state so that we can
add, remove, and update the values.
return (
<form>
<div className='flex items-center w-full max-w-lg gap-2 p-5 m-auto'>
<input
type='text'
placeholder='start typing ...'
className='w-full px-5 py-2 bg-transparent border-2 outline-none
border-zinc-200 rounded-xl placeholder:text-zinc-500 focus:border-zinc-700'
value={title}
onChange={event => setTitle(event.target.value)}
/>
<button
onClick={handleSubmit}
type='submit'
className='px-5 py-2 text-white bg-blue-500 border-2 border-
transparent rounded-lg hover:bg-blue-700'
>
Submit
</button>
</div>
</form>
);
};
Most of the code for AddTask is copied from the <form> you
created in the App component earlier.
One difference is that you need to create a state for the title
value, and then add the value and onChange props to the <input>
element.
First, you create a newTask object and input the title, completed,
and id properties. The id value is filled using the nanoid() so
that each task has a unique identifier.
After that, call the setTasks() function to add the newTask object.
If you recall, the setSomething() function returned by the
useState hook always receives the previous state value.
Since the tasks state is an array, you can use the spread
operator to expand the array, then add the newTask object next
to it:
And that’s it. Once the task is added, call setTitle() to reset the
input and show a toast to the user.
Import the nanoid function from the nanoid library at the top of
the file:
import { nanoid } from 'nanoid';
// index.jsx
export { AddTask } from './AddTask';
Without the index.jsx file, you need to specify the full path as
'./components/AddTask' at the from part.
Oh, and we also need to add the <Toaster> component from the
react-hot-toast package so that the toast will show on the
screen.
Add the component above the task title as follows:
You can test the application on the browser and see if you can
add a new test to it.
To prevent the App.jsx file from going too long, let’s separate
this component into its own file named TaskList.jsx in the
components/ folder.
Most of the import statements for React Icons are moved here,
so you can remove the same imports from the App.jsx file.
function App() {
// useState
return (
<>
<Toaster position='bottom-right' />
{/* Task Title */}
{/* ... */}
<AddTask setTasks={setTasks} />
<TaskList tasks={tasks} setTasks={setTasks} />
</>
);
}
The code above uses the Array map() method to iterate over the
tasks and find the task whose id value matches the task we
want to update.
Then, we call the setTasks() function to update the state and the
toast.success() method to notify the user.
return(
<ul className='grid max-w-lg gap-2 px-5 m-auto'>
{tasks.map(task => (
<li
key={task.id}
className={`p-5 rounded-xl bg-zinc-200 ${
task.completed? 'bg-opacity-50 text-zinc-500':''
}`}
>
<div className='flex flex-col gap-5'>
<span
style={{
textDecoration: task.completed ? 'line-through' : 'none',
}}
>
{task.title}
</span>
<div className='flex justify-between gap-5'>
<button>
{task.completed ? (
<span className='flex items-center gap-1 hover:underline'>
<TbRefresh />
Set Active
</span>
) : (
<span className='flex items-center gap-1 hover:underline'>
<BsCheck2Square />
Set Completed
</span>
)}
</button>
<div className='flex items-center gap-2'>
<button
className='flex items-center gap-1 hover:underline'
>
<FaRegEdit />
Edit
</button>
<button
className='flex items-center gap-1 text-red-500
hover:underline'
>
<RiDeleteBin7Line />
Delete
</button>
</div>
</div>
</div>
</li>
))}
</ul>
)
And that’s it. You should be able to toggle the task status now.
Deleting a Task
The next step is to create the delete functionalities. Let’s write a
function that removes a matching task in the TaskList
component:
Editing a Task
The final step is to implement the edit function. Let’s create two
states that will help in editing a component:
The editTaskId will store the id of the task we want to edit, and
the editTaskTitle will store the new title for the task.
<button
onClick={() => handleEditTask(task.id, task.title)}
className='flex items-center gap-1 hover:underline'
>
<FaRegEdit />
Edit
</button>
<ul>
{editTaskId === task.id ? (
<div className='flex gap-2'>
<input
type='text'
className='w-full px-5 py-2 bg-transparent border-2 outline-none
border-zinc-200 rounded-xl placeholder:text-zinc-500 focus:border-zinc-700'
value={editTaskTitle}
onChange={event => setEditTaskTitle(event.target.value)}
/>
<button
className='px-5 py-2 text-white bg-green-500 border-2 border-
transparent rounded-lg hover:bg-green-700'
onClick={() => editTask(task.id)}
>
Update
</button>
</div>
) : (
// the whole <li> element
)}
</ul>
By using the ternary operator, you can render the edit state
only when the editTaskId contains a valid id value.
// AddTask.jsx
<input
type='text'
placeholder='start typing ...'
className='w-full px-5 py-2 bg-transparent border-2 outline-none border-
zinc-200 rounded-xl placeholder:text-zinc-500 focus:border-zinc-700'
value={title}
onChange={event => setTitle(event.target.value)}
/>
// TaskList.jsx
<input
type='text'
className='w-full px-5 py-2 bg-transparent border-2 outline-none border-
zinc-200 rounded-xl placeholder:text-zinc-500 focus:border-zinc-700'
value={editTaskTitle}
onChange={event => setEditTaskTitle(event.target.value)}
/>
<input
{...props}
...
/>
// AddTask.jsx
import { TaskInput } from './TaskInput';
// ...
<TaskInput
placeholder='start typing ...'
value={title}
onChange={event => setTitle(event.target.value)}
/>
// TaskList.jsx
import { TaskInput } from './TaskInput';
// ...
<TaskInput
value={editTaskTitle}
onChange={event => setEditTaskTitle(event.target.value)}
/>
If you need to check your work, you can find the source code
for this chapter at https://github.com/nathansebhastian/react-
todo-2
Summary
You’ve learned a lot in this chapter! By now, you’ve seen how
React states and props can be used to control the values that are
important for the application.
While you can put everything in a single .jsx file, a good React
application is usually split between many components to make
it easier when working with a team.
In the next chapter, we’re going to use the Context API to lift the
tasks state up from local to global.
CHAPTER 12: THE CONTEXT API
So far, you’ve used the states and props to pass data in a top-
down approach as follows:
When using the context API, the data will be created and
managed inside the API instead of the top component:
When implemented, the Context API is a component that wraps
your React application (usually called the Provider component).
To get the values from this API, you need to use a special hook
called the useContext hook.
Let’s see how to implement the Context API in our To-do List
application so that it makes sense.
First, create a folder named context/ inside the src/ folder then
create a new file named TasksContext.jsx.
return (
<TaskContext.Provider>{props.children}</TaskContext.Provider>
);
};
const value = {
tasks,
addTask,
updateTaskStatus,
editTask,
deleteTask,
};
// ...
}
If you try to add a new task now, you’ll notice that the title
local state doesn’t reset after you add the task.
function runEditTask() {
editTask(editTaskId, editTaskTitle);
setEditTaskId(null);
setEditTaskTitle('');
toast.success('Task title updated!');
}
<button
className='...'
onClick={() => runEditTask(task.id)}
>
Update
</button>
The runEditTask() function will reset the local states and create
a toast notification when you click the Update button.
Update Components Reference in App
Component
Because the child components are accessing data directly from
the Context API, you no longer need to pass the props when
using these components.
<TaskProvider>
{/* Update AddTask and TaskList: */}
<AddTask />
<TaskList />
</TaskProvider>
You’ve replaced the local tasks state with the Context API. Very
cool!
If you need the source code for this application to review the
changes, you can get it from
https://github.com/nathansebhastian/react-todo-3
Summary
In this chapter, we’ve seen how we first created a local state in
the App component before replacing it with the Context API.
In this chapter, we’re going to create a simple JSON file that will
serve as the database for the To-do List. We’re going to learn
how to perform CRUD operations using the Fetch API to create,
read, update, and delete a task.
{
"tasks": [
{
"title": "Learn Swimming",
"completed": true,
"id": 1
},
{
"title": "Learn Cooking",
"completed": false,
"id": 2
}
]
}
This JSON file will serve as the database of our application. This
file will be updated when we add, update, or delete a task.
The next step is to run this JSON file as a server. To do so, you
need to use Node Package Manager (or NPM for short) to install
the json-server package.
json-server db.json
You can do this in the TaskContext file you created earlier. First,
create a constant variable that will store the base API URL as
follows:
useEffect(() => {
getData();
}, []);
// ...
}
Because we’re going to source the state from the data returned
by the json-server, we initialize the tasks state as an empty
array, removing the hard-coded values.
Next, we create the getData() function as an asynchronous
function and call the fetch() function to get the tasks data.
Now if you run the application, you can see that the tasks data
are retrieved from the db.json file. You need to update the state
functions next.
if (response.ok) {
toast.success('Task status updated!');
getData();
}
};
Here, you can see that once we create the newTask object, we call
the Fetch API to send a POST request to add the new task to the
database.
taskToUpdate.completed = !taskToUpdate.completed;
if (response.ok) {
toast.success('Task status updated!');
getData();
}
};
The find() method will return a task object that has the same id
value as the one you want to update.
After that, a PATCH request is sent to the API URL, but you also
need to add the id of the task you want to change. This is why
the URL in fetch() is BASE_API_URL/{taskId} as shown above.
Updating editTask() Function
The editTask() function is similar to the updateTaskStatus()
function, except that we’re updating the task title instead of the
completed status:
taskToUpdate.title = editTaskTitle;
if (response.ok) {
toast.success('Task Deleted!');
getData();
}
}
};
And now the To-Do List application will source the tasks data
from the db.json file.
Summary
When developing an application, you will frequently write
some code that acts as a scaffold. They are there to help you
finish the application, but they will be removed when you have
the completed version.
In this chapter, you once again updated parts of the code so that
the tasks data can be sourced from a remote API.
const value = {
tasks,
addTask,
updateTaskStatus,
editTask,
deleteTask,
filterTasks
};
The next step is to create a filter component that will make use
of the filterTasks() function.
return (
<div className='flex justify-end items-center max-w-lg px-5 m-auto my-2'>
<label
htmlFor='filter'
className='w-28 font-medium text-gray-900 text-right pe-2'
>
Filter Tasks:
</label>
<select
id='filter'
className='w-28 bg-gray-50 border border-gray-300 text-gray-900
rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5'
defaultValue='All'
onChange={event => filterTasks(event.target.value)}
>
<option value='all'>All</option>
<option value='completed'>Completed</option>
<option value='active'>Active</option>
</select>
</div>
);
Now that you have the filter component ready, import the
component into the App.jsx file.
And that’s it. Now you can filter the tasks shown by the
application by using the select component.
To see the full source code of the To-do List application, you can
go to https://github.com/nathansebhastian/react-todo-5
Summary
Congratulations on finishing your second React Application! By
building the To-do List application, you’ve seen how an
application can be build from basic components.
Besides using Next.js, you can also use the MERN stack to create
a full-stack React application.
You might also want to learn about TypeScript, the library used
to add static typing to JavaScript applications.
I’m currently planning to write books on these topics, so you
might consider subscribing to my newsletter to know when I
release the books at https://codewithnathan.com/newsletter
If there are some things you wish to tell me about this book, feel
free to email me at nathan@codewithnathan.com. I’m very
open to feedback and eager to improve my book so that it can
be a great resource for people learning to code.
If you didn’t like the book, or if you feel that I should have
covered certain additional topics, please let me know in the
email.
By Nathan Sebhastian
https://codewithnathan.com