Irshadi Bagasputro
05 March 2023 · 8 min read
Before we're going to talk about most use cases of React Hooks, I'm gonna tell you what is React Hooks is,—well, I'm not gonna write about what is react hooks in a full manner of explanation here. But I can tell you what the summary is.
By definition from reactjs official website
Hooks are functions that let you “hook into” React state and lifecycle features from function components.
So in short hooks are functions that lets you use react state and lifecycle from components without making it a class components. It completely replaces Classes React/OOP paradigm with a Functional Programming paradigm.
Surely, it raised one question Why do we use hooks now ? or Why Dan ditch Classes based React for a Functional one ? I have a good article (with full proof and explanation) that you can read here. But the main takeaway is React props or state are always immutable, but the concept of this
in Javascript has always been mutable.
By doing this, you'll most likely to prevent early return of your components. And your hooks are called in the same order each time your component renders.
So, without further ado, let's talk about React Hooks, and their implementations.
The most common type of hooks that you'll use is useState
. Pre-Hooks component has a built-in state object where you store property values that belongs to the component. useState
basically do the same, it is used to store your data, so every-time your component re-render, you can store your data safely inside. If you want to store a data, useState
is your goto guy.
Here's a text input component, designated to change my name, you can implement useState Hooks just like this.
1
import React from "react";2
3
const ReactComponent = () => {4
// Call Hooks ALWAYS on Top Level on the Component5
const [name, setName] = React.useState("");6
7
const handleOnChangeName = e => setName(e.target.value)8
9
return <input onChange={handleOnChangeName} />;10
};
useState
hooks can holds anything, not just a primitive javascript data-type, this means you can also stores array or object. It also can be used with a callback function.
1
import React from "react";2
3
const _userInfoInitialValue = {4
name: "Irshadi",5
age: 25,6
address: "Cilandak"7
};8
9
const ReactComponent = () => {10
const [userInfo, setUserInfo] = React.useState(_userInfoInitialValue);11
12
const handleOnChangeUserInfo = ({ key, value }) => {13
setUserInfo(currentUserInfo => {14
// currentUserInfo holds current state of the data in the hooks15
return {16
...currentUserInfo,17
[key]: value18
};19
};20
21
return (22
<input23
onChange={e => {24
handleOnChangeUserInfo({ key: "address", value: e.target.value )}25
}} />26
);27
};
Another common Hooks is useEffect
, It used when you want to perform data fetching, subscriptions or anything that relates to DOM changes. React called this a side effect—hence effect for short, because they can and will affect components. In React classes we use componentDidMount
, componentDidUpdate
and componentWillUnmount
. The useEffect
Hook replaces all of 3 above (We'll get to that in a minute).
When you call useEffect, you’re telling React to run your “effect” function after flushing changes to the DOM.
When you're using useEffect
, you tell React that your component needs to do something after render. By default, useEffect
will run on the first and after every update.
Below, is a component to show whether the user has logged in or not. On component render React will Render this Component, if the isLoggedIn
state is truthy.
1
import React from "react";2
3
const ReactComponent = () => {4
const [isLoggedIn, setIsLogin] = React.useState(false);5
6
// Runs on First and After every Update7
React.useEffect(() => {8
// Do Side Effects9
setIsLogin(true);10
});11
12
return (13
<div>14
{/* JSX Render */}15
</div>16
);17
18
};
The useEffect
hooks receives two arguments, a function and it signature—array of dependencies. What ? what's that ? Well, in short it's a way of React to watch things to evaluate things and ask itself, "Should I re-render ?". Here's an example
1
import React from "react";2
3
const ReactComponent = () => {4
const [count, setCount] = React.useState(0);5
const [labelText, setLabelText] = React.useState(`Click ${count} Times`);6
7
// Runs on when counter value changes8
React.useEffect(() => {9
// Do Side Effects10
setLabelText(`Click ${count} Times`);11
}, [count]);12
13
return (14
<div>15
<span>Count {count} times</span>16
<button onClick={() => setCount(count++)}>Click Me!</button>17
{/* JSX Render */}18
</div>19
);20
};
What the code did above, is to tell React to compare the value of state count
, whether there's a change between previous render and next render (React does this by comparing DOM and V-DOM). If there aren't any differences in dependencies — in this case, count
state remains the same, React will skip the side effects. If there are multiple items in the dependencies, React will re-run the effect even if just one of them is different. Oh and you might want to avoid object as dependency.
useEffect
can also be used for cleanup state or any values in the component. React performs the cleanup when the component unmount. All we need to do is to return a function on useEffect
.
1
import React from "react";2
3
const ReactComponent = () => {4
const [isLoggedIn, setIsLogin] = React.useState(false);5
const [count, setCount] = React.useState(0);6
const [labelText, setLabelText] = React.useState(`Click ${count} Times`);7
8
// Runs on when counter value changes9
React.useEffect(() => {10
setLabelText(`Click ${count} Times`);11
}, [count, isLoggedIn]);12
13
// On component unmount reset every state back to 014
React.useEffect(() => {15
return () => {16
setCount(0);17
setLabelText(`Click 0 Times`);18
};19
}, [isLoggedIn]);20
21
return (22
<div>23
<button onClick={() => setIsLogin(false)}>Log Out</button>24
{/* JSX Render */}25
</div>26
);27
};
Remember this code ? Take a second look.
1
import React from "react";2
3
const ReactComponent = () => {4
const [count, setCount] = React.useState(0);5
const [labelText, setLabelText] = React.useState(`Click ${count} Times`);6
7
// Runs on when counter value changes8
React.useEffect(() => {9
// Do Side Effects10
setLabelText(`Click ${count} Times`);11
}, [count]);12
13
return (14
<div>15
<span>Count {count} times</span>16
<button onClick={() => setCount(count++)}>Click Me!</button>17
{/* JSX Render */}18
</div>19
);20
};
Wait a minute, I realize something, the code above can be optimized, we can eliminate this labelText
state.
1
import React from "react";2
3
const ReactComponent = () => {4
const [count, setCount] = React.useState(0);5
6
const generateLabelText = () => `Click ${count} Times`7
8
// Runs on when counter value changes9
React.useEffect(() => {10
generateLabelText();11
}, [count, generateLabelText]);12
13
return (14
<div>15
<span>Count {count} times</span>16
<button onClick={() => setCount(count++)}>Click Me!</button>17
{/* JSX Render */}18
</div>19
);20
};
Well, technically you can, but you can't. Don't believe me ? Go try for yourself.
So you encountered an infinite loop, but why does this happen ? how can I fix this ? Okay, Let's start with what's happen. What just happen is—since we put a function in dependencies array—, React is comparing generateLabelText
function from previous render, with the next render and—here's the interesting part— functions will be re-created on every render. Thus, React detect an endless state update which cause infinite loop.
There's a several way you can fix this. I'll show you in a minute, but first some of you may be wonder, why you put a function in a dependency array ? The answer is Why not ? and linter told me so. In React there are some rules that you'll need to follow in order for Hooks to work. React has some built-in lint-ing rules that will tell you when you're doing certain things wrong.
1
// A Quick Fix would be2
React.useEffect(() => {3
const generateLabelText = () => `Click ${count} Times`4
generateLabelText();5
}, [count]);
But what happen if we need generateLabelText
function elsewhere ? Enter useCallback
Hooks.
What useCallback
does, is to create a memoization of your function. This means when React compares between next and previous render, your function stays the same. In other words; React doesn't re-create generateLabelText
function on every render. useCallback
signature is similar with useEffect
you have second arguments of dependencies, which acts the same like useEffect
.
1
import React from "react";2
3
const ReactComponent = () => {4
const [count, setCount] = React.useState(0);5
6
// Wrap fn inside a useCallback Hook7
const generateLabelText = React.useCallback(() => {8
return `Click ${count} Times`9
}, [count])10
11
// Runs on when counter value changes12
React.useEffect(() => {13
generateLabelText();14
}, [count, generateLabelText]);15
16
return (17
<div>18
<span>Count {count} times</span>19
<button onClick={() => setCount(count++)}>Click Me!</button>20
{/* JSX Render */}21
</div>22
);23
};
The last one I want to talk about is useRef
basically it can be used to store a value, that can be mutated, on every render. The value of your "reference", will stay the same between component re-render. On top of that, updating or mutating your value in useRef
does not trigger a re-render. Just remember ! Reference in useRef
must be updated either; inside a useEffect
or a function.
My favorite common use cases for useRef
are
Preserve Original Value
Our first—and my common use case is to preserve value within every render. Imagine you have GET
API, of some details and either PUT
or PATCH
API. You need to make a change first from the retrieved value from GET
API, before you can call PUT
or PATCH
API. And the submit button have a condition if nothing changes, you can't call PUT
or PATCH
API. In short I want to compare original value— from the API, with my modified value — from the state; to control whether the button is disabled or not.
1
import React from "react";2
3
const ReactComponent = () => {4
const [data, setData] = React.useState();5
const ref = React.useRef();6
7
// Function for GET some data8
const getData = React.useCallback(async () => {9
// Fetch GET API Here10
const data = await get();11
12
// Save an original value of given data13
ref.current = data;14
setData(data);15
}, [])16
17
const handleOnDataInput = () => {18
let newData = {};19
// Process Data Here20
21
// This will change state data, so the JSX <button /> above, will be enabled22
setData(newData);23
};24
25
React.useEffect(() => {26
getData();27
}, [getData]);28
29
return (30
<div>31
<Component data={data} />32
<input onChange={handleOnDataInput} />33
{/* Logic for disabled condition */}34
<button disabled={isEqual(ref.current, data)} onClick={editDataToBackend}>35
Save36
</button>37
</div>38
);39
};
Okay, imagine we have this code. On first GET
API call, your data is still an original data. const data = { name: "Irshadi Bagasputro", age: 25 };
and it stores at the useRef
, when you're trying to change your state (ie: change age). The state will be something like this:
1
// I did something, in this case change state value, to:2
data = {3
name: "Irshadi Bagasputro",4
age: 265
};6
7
// But the reference value, is an mutable object,8
// and we don't call it anywhere,9
// except on first Render (in useCallback Hooks that only runs one)10
11
// Comparing Original Value and Modified Value12
13
console.log(ref.current)14
// { name: "Irshadi Bagasputro", age: 25 }15
16
console.log(data)17
// { name: "Irshadi Bagasputro", age: 26 }18
19
**// Hence, the button will be enabled**
Capture and Accessing DOM elements
When you use useRef
you are given the instance value of the component the ref is attached to. This allows you to interact with the DOM element directly.
My second use case is to store DOM elements. Basically it's like document.querySelector
in React way.
1
import React from "react";2
3
const ReactComponent = () => {4
const ref = React.useRef();5
6
return (7
<div ref={ref}>8
{/* JSX Render */}9
</div>10
);11
};
When you try to console.log(ref.current)
it'll become a DOM.
1
// This log2
console.log(ref.current);3
4
// Will display you this5
<div class="my_class" />6
7
// And you can basically do anything with vanilla DOM8
9
console.log(ref.current.getClientBoundingRect());10
// { x: 120, y: 120 }
Okay, I guess that's all I can share. I'll see you guys on my next writing ! CIAO~