Front End

Things i wish i'd known before dive to React Hooks

Irshadi Bagasputro

05 March 2023 · 8 min read

React Hooks

Introduction to Hooks

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

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.

Why do I need to use Hooks ?

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.

Important things when using React Hooks

  1. Always call hooks in the top level of your component.
  2. Don't call hooks in a loop, conditions, or nested function.

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.

React.useState

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 Component

5

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 hooks

15

return {

16

...currentUserInfo,

17

[key]: value

18

};

19

};

20

21

return (

22

<input

23

onChange={e => {

24

handleOnChangeUserInfo({ key: "address", value: e.target.value )}

25

}} />

26

);

27

};

React.useEffect

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'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 Update

7

React.useEffect(() => {

8

// Do Side Effects

9

setIsLogin(true);

10

});

11

12

return (

13

<div>

14

{/* JSX Render */}

15

</div>

16

);

17

18

};

Array Dependencies

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 changes

8

React.useEffect(() => {

9

// Do Side Effects

10

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.

Cleanup Values

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 changes

9

React.useEffect(() => {

10

setLabelText(`Click ${count} Times`);

11

}, [count, isLoggedIn]);

12

13

// On component unmount reset every state back to 0

14

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

};

React.useCallback

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 changes

8

React.useEffect(() => {

9

// Do Side Effects

10

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 changes

9

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 partfunctions 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 be

2

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 Hook

7

const generateLabelText = React.useCallback(() => {

8

return `Click ${count} Times`

9

}, [count])

10

11

// Runs on when counter value changes

12

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

};

React.useRef

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

  1. 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 data

    8

    const getData = React.useCallback(async () => {

    9

    // Fetch GET API Here

    10

    const data = await get();

    11

    12

    // Save an original value of given data

    13

    ref.current = data;

    14

    setData(data);

    15

    }, [])

    16

    17

    const handleOnDataInput = () => {

    18

    let newData = {};

    19

    // Process Data Here

    20

    21

    // This will change state data, so the JSX <button /> above, will be enabled

    22

    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

    Save

    36

    </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: 26

    5

    };

    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 Value

    12

    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**
  2. 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 log

    2

    console.log(ref.current);

    3

    4

    // Will display you this

    5

    <div class="my_class" />

    6

    7

    // And you can basically do anything with vanilla DOM

    8

    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~


© 2023 irshadibagas.com