Originally I have submitted this tutorial to Facebook Community Challenge 2020, you can access and read it from this link.
React is a Javascript library for building user interfaces. You may have worked with class components, and didn't understand why developers are using hooks. As a beginner, it's hard to know which one to use, for example, there are legacy codes written in class components, and you need to refactor it to functional components using Hooks, or you just want to learn the new React, and don't want to invest time learning class components. If you are thinking like this, you are in the right place. This tutorial will show you the details about using useState
and useEffect
hooks, as you progress you can go on with different hooks, and libraries. React is a huge library and you will be shocked that there are so many different things that you can do with React only. So, fasten your seatbelts, and enjoy the ride.
What we will cover
Throughout this tutorial, we’ll learn how to set state using the useState
and useEffect
Hooks. We’ll create different components and for the last component, we will combine these two hooks and create a recipe app that will fetch new recipes from an API. More specifically, we will learn how to:
- use
useState
with an array/object for the default value - use
useEffect
without a Dependency Array, with an Empty Dependency Array, with a Non-empty Dependency Array, and with a cleanup function - fetch an API with
useEffect
By the end of the tutorial, you will have the following skill sets:
- The hands-on practical and real-life scenario of basic React Application using React Hooks.
- You will manage state in a functional component using Hooks, and you’ll have a foundation for more advanced Hooks such as
useCallback
,useMemo
, anduseContext
.
Here is the live demo of the end result.
Prerequisites
- Basic familiarity with HTML & CSS.
- Basic knowledge of JavaScript ES6.
- Basic understanding of the DOM.
- Basic React knowledge like props, components, one way-data-flow
What Are React Hooks?
Hooks are a new addition in React 16.8. With the help of hooks, we can use state and other React features without writing a class.
Hooks allow for attaching reusable logic to an existing component and use state and lifecycle methods inside a React functional component.
We can organize the logic inside a component into reusable isolated units
, and we have a better separation of concerns.
React Hooks makes developing apps easier with less complexity. It improves the readability and organization of components.
We can create custom hooks to reuse code across our app.
I want to start our tutorial with a general overview of our hooks. This will you a big picture of hooks, then we will dig deeper into our two commonly used hooks. You can just skim over these and use them as a reference when you need them. This may be overwhelming, but no need to worry about it right now.
-
useState
is the most common hook that you will see. It is thestate hook
for declaring the state in our components. -
useEffect
is used for side effects like fetching data from an API. -
useRef
is used to allow access directly to an element in the DOM and to create a mutable ref object that won't trigger a rerender. -
useContext
allows us to easily work with the React Context API (solving the prop drilling issue). -
useReducer
is an advanced version ofuseState
for managing complex state logic. It’s quite similar to Redux. -
useMemo
returns a value from a memoized function. -
useCallback
returns a function that returns a cacheable value. Useful for performance optimization if you want to prevent unnecessary re-renders when the input hasn’t changed. -
useLayoutEffect
similar touseEffect
, they differ in when they trigger. -
useImperativeHandle
to customize the instance value that’s exposed to parent components when usingref
. -
useDebugValue
displays a label for custom Hooks in React Developer Tools.
In this tutorial, we will focus on the most common hooks: useState
and useEffect
. But first, let's start with why we need hooks in the first place.
Why Hooks?
Before Hooks:
- We would need to understand how this keyword works in Javascript and to remember to bind event handlers in
class components
. - A common way to attach logic externally to a component was to use the
render props
orHigher-Order Components
pattern.
We needed to share stateful logic in a better way. React is designed to render components, and it doesn't know anything about routing, fetching data, or the architecture of our project.
There wasn't a particular way to reuse stateful component logic and this made the code harder to follow.
So, React Hooks came to the rescue.
Hooks are just functions that are exported from the official React page. They allow us to manipulate components in a different manner.
There are some rules about how to use hooks. The following rules are:
- Only call hooks at the top level of the component.
- Don't call hooks inside loops, conditionals, or nested functions.
- Only call hooks from React functional components.
- Call them from within React functional components and not just any regular Javascript function.
- Hooks can call other Hooks.
You may ask, Should I need to change my class components to hooks? Actually NO, we can still use class components as 16.8 is backward compatible.
Application Tools
- [x] Install NodeJS and make sure it is the LTS(long term support) version. LTS version is a less stable version of NodeJS. We will use NPM (node package manager) and we will use it to install create-react-app.
- [x] Install your preferred code editor or IDE. I will be using Visual Studio Code. You can download it from this website. It is free to use.
- [x] create-react-app is an npm package that we can bootstrap our React application without any configuration.
How to Install React Hooks?
You need to either upgrade the version of React and React-DOM to 16.8.2
or create a new React project using Create React App.
In this tutorial, we’ll use Create React App to create a new React project.
Open up your terminal and run the following to create the new project:
# cd into the directory you want to create the project.
cd desktop
# type this command to install create-react-app, you can give any name for the app.
npx create-react-app myApp
# Let's go inside our project folder, type the name of our project, and `cd` into it.
cd myApp
# open the project files with Visual Studio or any code editor
# start the app
npm start
Your default browser will open and you’ll see your new React app.
Now we can see our app is up and running. Before starting our app, let's make some cleanup and remove some of the files that we will not use.
Let's remove App.test.js, index.css, logo.svg, setupTests.js
from the src
folder. You can copy and paste the basic structure for App.js
and index.js
from the code snippets below.
// src/App.js
import React from 'react';
import './App.css';
function App() {
return <div></div>;
}
export default App;
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
Also, we can remove logo
files from the public
folder, now my files are looking like this:
Throughout this tutorial, we will create multiple components and you need to import the components to App.js
to see how it is working. I have used react-router-dom
to show all the components in one app, but we will not talk about routing in this tutorial. That's why you need to create a folder under src
directory named components
and create the components there, then import it to App.js
. Example:
// src/App.js
import React from 'react';
// import the new component here
import StateHook from './components/StateHook';
import './App.css';
function App() {
return (
<div>
{/* render the component */}
<StateHook />
</div>
);
}
export default App;
Styling the Application
I have used Semantic UI and custom CSS for styling. For Semantic UI, I have added a link
tag inside my public > index.html
file like this:
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet" />
className
attribute values are coming from Semantic UI or CSS. You don't need to focus on those.
For the CSS code, you can copy-paste these inside App.css
file.
/* src/App.css */
body {
padding: 10px;
font-family: sans-serif;
background-color: #f69e9e;
line-height: 1.2;
}
.container {
text-align: center;
margin-top: 5rem;
width: 90vw;
margin: 0 auto;
max-width: 1170px;
min-height: 100vh;
}
h1 {
color: #371e30;
letter-spacing: 10px;
text-transform: uppercase;
margin: 0 0 10px;
}
h2 {
font-weight: bold;
font-size: 1em;
line-height: 1.2em;
padding: 0;
color: #222;
font-size: 30px;
}
a {
text-decoration: none;
color: #222;
font-weight: 600;
}
ul {
vertical-align: bottom;
margin: 0 20px;
padding: 0 0 25px 0;
text-align: left;
}
p {
font-weight: bolder;
font-size: 1em;
text-align: left;
}
input[type='text'] {
width: 60%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border-radius: 4px;
box-sizing: border-box;
background: #fff;
}
.btn {
display: block;
margin: 0 auto;
padding: 0.25rem 0.75rem;
border-color: transparent;
text-transform: capitalize;
font-size: 1.4rem;
margin-top: 2rem;
cursor: pointer;
background-color: #ddd;
color: black;
}
.btn:hover,
a:hover {
border: 1px solid #df57bc;
background-color: #df57bc;
padding: 5px;
color: #fff;
}
.recipe {
border-radius: 10px;
margin: 40px;
min-width: 40%;
padding: 40px;
max-width: 400px;
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
}
Now, with this, we are ready to go. 🥳
The useState
Hook
State helps build highly performant web apps. To keep track of our application logic, we need to use useState
. We can reflect any UI(user interface) changes via changes in state.
useState
function lets us use state in a functional component.
In order to use useState
in our component, we have to import useState
first. useState
is a named export; so, we will export it with curly braces.
import React, { useState } from 'react';
Let's make an example of how to use useState
.
// src/components/StateHook.js
import React, {useState} from 'react';
const StateHook = () => {
const [title, setTitle] = useState('hello world');
// update the state with setTitle function
const handleClick = () => {
setTitle('React is cool');
};
return (
<div className="container">
<h2>{title}</h2>
<button type="button" onClick={handleClick} className="btn">
Change title
</button>
</div>
);
};
export default StateHook;
useState
returns an array of two items:
- the first element is the current value of the state.
- the second is a state setter/updater function, which we use to update our state.
In short, state tracks the value of our state. The setter function updates the state and rerenders JSX elements.
// destructuring an array
// initial state is 'hello world'
const [title, setTitle] = useState('hello world');
Developers generally prefer array destructuring with useState
hook or we need to write more verbose code like this:
const items = useState('hello world');
const title = items[0];
const setTitle = items[1];
You may ask, How React knows when to render? React components will only rerender when their props or state have changed. Props are passed into a component and read-only, whereas a state holds information about the component, and can be updated. During the initial render, the returned state is the same as the value passed as the first argument (initialState).
So, here we updated our state with the setTitle
setter function and passed a different string inside of it. When the button gets clicked, we are updating the state with the onClick event handler
. The setTitle
function accepts a new state value and rerenders the component.
In class components, a state is always an object, with the useState
hook, the state does not have to be an object. Instead, you can break up state into multiple pieces that you can update independently.
useState with objects
What I mean by the title is we will create an object inside our useState
hook, instead of passing a string. The initial value of useState
can be of any type, like an array, an object, a boolean, a number, a string, etc.
// src/components/StateHookObject.js
import React, {useState} from 'react';
const StateHookObject = () => {
// pass an object for the initial state
const [name, setName] = useState({firstName: '', lastName: ''});
return (
<form>
<input
type="text"
value={name.firstName}
// set firstName to whatever is typed inside the input field
onChange={(e) => setName({firstName: e.target.value})}
/>
<input
type="text"
value={name.lastName}
// set lastName to whatever is typed inside the input field
onChange={(e) => setName({lastName: e.target.value})}
/>
<h2>First name is: {name.firstName}</h2>
<h2>Last name is: {name.lastName}</h2>
</form>
);
};
export default StateHookObject;
Now, let’s break down the code above to explain what we’ve added and how it works.
- importing the
useState
hook from React - creating a new constant that returns
name
andsetName
fromuseState
. - initializing the
useState
hook with an object. - create a
form
to display our inputs andh2
tags - add
value
property andonChange
event handler to our inputs.e.target.value
will give us the value inside the input field.
The important part about this component, we need to focus on the onChange
event handler. onChange
event fires whenever the user types in something.
Whenever the first input value changes, we update the firstName
property, and when the second input value changes, we update the lastName
property.
Okay, everything looks perfect. Now, let's test our code.
We have a problem with updating our states; so, as you can see, we can update both input fields; but when we switch between them we cannot keep track of our old state.
Let's add this one line of code to see what is happening.
// src/components/StateHookObject.js
// ...
<h2>Last name is: {name.lastName}</h2>
// add this line to your code
<h2>{JSON.stringify(name)}</h2>
</form>
When we type for the first name input, the last name input is disappearing. Because state doesn't automatically merge and update the state. useState
does not "merge" its arguments with the old state. They just set the state. Every time, with every rerender we don't mutate our state, we get a completely new state, we can change our state with the setter function.
In class components setState
will merge the state; useState
hook will not merge the state. To handle this, we will use the spread operator
to merge. With this, the setter object will copy everything inside the name
object, and overwrite the firstName
or lastName
fields with a different value.
Let's see this in our code:
// src/components/StateHookObject.js
// ...
return (
<form>
<input
type="text"
value={name.firstName}
// add the spread operator
onChange={(e) => setName({...name, firstName: e.target.value})}
/>
<input
type="text"
value={name.lastName}
// add the spread operator
onChange={(e) => setName({...name, lastName: e.target.value})}
/>
<h2>First name is: {name.firstName}</h2>
<h2>Last name is: {name.lastName}</h2>
<h2>{JSON.stringify(name)}</h2>
</form>
);
- We
shouldn't mutate the state
in our components. - We need to pass the previous state by the state setter(with the spread operator).
useState with arrays
Now, we will make another component that we will use an array for the initial state. Let's see what will happen.
// src/components/StateHookArray.js
import React, { useState } from 'react';
const StateHookArray = () => {
const [ items, setItems ] = useState([
{ id: 1, listItem: 'go on a holiday' },
{ id: 2, listItem: 'go hiking' },
{ id: 3, listItem: 'learn React Hooks' }
]);
// remove items
const removeItem = (id) => {
setItems(items.filter((item) => item.id !== id));
};
const addItem = () => {
setItems([
// don't mutate the array, use spread operator to get the previous state
...items,
// add new item
{
id: 4,
listItem: 'meet deadlines'
}
]);
};
return (
<div className="ui container">
<div className="ui grid">
{items.map((item) => {
const { id, listItem } = item;
return (
<div key={id} className="row">
<h2 className="five wide column">{listItem}</h2>
{/* when it is clicked, remove the individual item */}
<button className="three wide column btn" onClick={() => removeItem(id)}>
remove
</button>
</div>
);
})}
</div>
{/* when it is clicked, empty the whole array */}
<button className="btn" onClick={() => setItems([])}>
Delete all
</button>
{/* when it is clicked, add one new item to the list */}
<button className="btn" onClick={() => addItem()}>
Add Item
</button>
</div>
);
};
export default StateHookArray;
Let’s break down the code above to explain what we’ve added and how it works.
- importing the
useState
hook from React - creating a new constant that returns
items
andsetItems
fromuseState
. - initializing the
useState
hook with an array of objects. - returning some JSX elements to display our array items and Semantic UI to add a grid system
- mapping over the array to get each array item
- adding a remove button for every item when it is clicked, we can remove the individual item
- adding a button with an
onClick
handler that invokes thesetItems
function ofuseState
with an empty array. So, we can remove everything from our array. - adding an add button, when it is clicked on it adds a new item. We merge the old state with the updated state with the help of ES6 spread operator.
And yes, we are done with the useState
hook. 🥳
Thanks for your time. Like this post? Consider buying me a coffee to support me writing more.
Top comments (0)