React Hooks

Kim Novak Categories: Back-end development, Tips and tricks Date 10-Jun-2019 7 minutes to read
React Hooks

Table of contents

    What are React Hooks?

    React Hooks is a new concept that came along with React version 16.8. They provide a way for Function Components to have some of the features that Class Components have, such as State and Lifecycle methods.

    react_hooks_news_details.jpg

    Before Hooks, there was no way to use State inside Function Components, and, if you had to use it, you had to rewrite your Component and implement it as a class.

    Why use React Hooks

    Before Hooks, there was no way to use State inside Function Components, and, if you had to use it, you had to rewrite your Component and implement it as a class. This brought in a lot of boilerplate code. With Hooks we can use State inside Function Components. They also provide a way to hook into Component’s Lifecycle, and have Context and Refs inside Function Components. Hooks also make your code cleaner because they reduce nesting that was previously made by Higher Order Components or Render props.

    Some of the problems that Hooks solve are:

    • Reusing stateful logic - by creating custom Hooks
    • Having State and Lifecycle inside Function Components
    • Having Context and Refs inside Function Components
    • The need to understand how Javascript this keyword works (may not necessarily be a problem)

    React Hooks Tutorial

    To get started with React Hooks, first make sure that you are using React version 16.8 or above. Open package.json file and check if both react and react-dom versions are 16.8 or above.

    useState Hook

    The useState Hook allows you to have the state inside a Function Component. This Hook is a part of the core React Library so we import it from react:

    import {useState} from 'react';

    Once imported, we can call it (notice: Hooks are functions). The syntax for calling useState is:

    const [state, setState] = useState(initialState);

    We can pass an argument to useState Hook. This argument represents the State’s initial value. Unlike State inside Class Components, State inside Function Components doesn’t have to be an Object. It can be a string, for example.

    useState returns a pair of Values:

    • The first value in a pair represents the current value of the state
    • The second value in a pair represents a function that we can use to change the state

    A convenient way to assign values returned by Hooks is by using Array Destructuring:

    const [state, setState] = useState(initialState);

    is equivalent to:

    const returnedValue = useState(initialState);const state = returnedValue[0];const setState = returnedValue[1];

    useState Hook Example

    In the following example, we will call useState hook and pass it the initial state. When the user clicks on the button, the value of the state changes by calling setState and passing the new value.

    import React, {useState} from 'react';const initialState = {    exampleAttribute: 'example'}function ExampleComponent(props) {    const [state, setState] = useState(initialState);    function changeState(event) {        setState({            exampleAttribute: 'new value'        });    }    return (        <React.Fragment>            {state.exampleAttribute}            <button                onClick={changeState}            >                Change State            </button>        </React.Fragment>    );}export default ExampleComponent;

    If we were to have multiple attributes inside the initialState object and update only the exampleAttribute, we would see that other attributes would change the value they had and become undefined. The function that changes the state doesn’t merge the previous with the new, like the setState in class Components did. If we want to preserve all of the values inside an object, we need to manually pass those values to the new state. The easiest way to do this is by using spread operator:

    const [state, setState] = useState({        exampleAttribute: 'example',        anotherAttribute: 'the other value'    });    function changeState(event) {        setState({            ...state,            exampleAttribute: 'new value'        });    }

    Multiple useState Hooks inside a component

    We can have multiple useState Hooks in the same component, unlike the single state object we had in class Components. The state value doesn’t have to be an Object. We will split the state from the previous example into multiple useState Hooks:

    import React, {useState} from 'react';function ExampleComponent(props) {    const [exampleAttribute, setExampleAttribute] = useState('example');    const [anotherAttribute, setAnotherAttribute] = useState('other value')    function changeExampleAttribute(event) {        setExampleAttribute('new value');    }    function changeAnotherAttribute(event) {        setAnotherAttribute('new other value');    }    return (        <React.Fragment>            {exampleAttribute}            {anotherAttribute}            <button                onClick={changeExampleAttribute}            >                Change Example Attribute            </button>            <button                onClick={changeAnotherAttribute}            >                Change Another Attribute            </button>        </React.Fragment>    );}export default ExampleComponent;

    useEffect Hook

    UseEffect Hook provides a way to hook into Component’s Lifecycle. UseEffect is called after each render, including the initial render.


    This Hook can have the same role as:

    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount

    This Hook is a part of the core React Library so we import it from react:

    import {useEffect} from 'react';

    Once imported, we can call it. The syntax for calling useEffect is:

    useEffect(effect, arrayOfValuesThatTriggerThisEffect);

    The first argument (the effect) is a function that will be executed each time the useEffect is triggered. It’s advisable to use the arrow function because it will always create a new function and make sure that we don’t access any stale values. The fact that it is named after the side-effects, or short “effects“ implies that the useEffect Hook is used to cause side-effects. The return value of the effect is the function that will do the clean up of the side-effect if needed (e.g. clear interval). This cleanup function will be called before next render. The idea of useEffect is to set an effect and then clean it up before the next render and replace it with the new effect. The cleanup function will also be called on component unmount, thus behaving like componentWillUnmout.

    The second argument is optional and represents an array of values that the effect subscribes to. Updating any of these values will trigger useEffect Hook and execute the function passed as the first argument. If the second argument is omitted, useEffect will run the effect after each render. If the value of the second argument is an empty array, useEffect behaves as componentDidMount – it will be called only after the initial render.

    useEffect Hook Example

    In the following example there is an input field for username value. Each time the input value changes, changeUsername handler is called and username value is updated. When the username value is updated, useEffect is triggered. If you follow what’s being logged into the console, you will see that ’useEffect cleanup’ is logged after each change.

    import React, {useState, useEffect} from 'react';function ExampleComponent(props) {    const [username, setUsername] = useState('username value');    useEffect(() => {        console.log(`the username value is: ${username}`)        return () => {            console.log(`useEffect cleanup`);        }    }, [username]);    function changeUsername(event) {        setUsername(event.currentTarget.value);    }    return (        <React.Fragment>            <p>                {username}            </p>            <input                 type="text"                value={username}                onChange={changeUsername}            />        </React.Fragment>    );}export default ExampleComponent;

    useContext Hook

    With useContext Hook we are able to consume values provided by Contexts within Function Components.
    This Hook is a part of the core React Library so we import it from react:

    import {useContext} from 'react';

    Once imported, we can call it. The syntax for calling useContext is:

    const valueFromContext = useContext(MyContext);

    The argument passed to useContext is the value returned by React.createContext. Make sure you are passing MyContext and not MyContext.Provider nor MyContext.Consumer.

    The value returned by the useContext Hook is the current value stored inside that Context. If the value inside the Context changes, the valueFromContext will reflect the new value.

    useContext Hook Example

    In the following example we will create ThemeContext with the default value ’dark’ and consume that value inside a Function Component using React Hooks.

    First Create Context

    import React from 'react';export const ThemeContext = React.createContext('dark');

    Then inside a Function Component import that Context

    import React, {useContext} from 'react';import { ThemeContext } from '../../contexts/ThemeContext';function Example(props) {    const value = useContext(ThemeContext);    return (        <React.Fragment>            {value}        </React.Fragment>    );}export default Example;

    Rules of Hooks

    As we’ve seen in useState section we can call multiple Hooks in the same Component. But, there are some rules we need to follow to do so.

    First Rule of Hooks

    Hooks can be called only at top-level. This means they cannot be called inside loops, conditions or nested functions. If Hooks are called at top-level we ensure that they are always called in the same order. React relies on this call order, because there is nothing except the call order that lets React know which state corresponds to which useState call. We don’t pass any key or anything similar to let React know which state is connected to which useState call. So, if we had three useState calls and put the second call inside a condition, and make that condition false after the first render, the state from the second useState will now correspond to the third State. This is not the desired behavior. If you need conditions, you can put them inside a Hook.

    React associates a list of internal ‘memory cells’ to each rendered component. These cells are Javascript objects. When we call Hooks, if it’s the initial render, React creates a new cell in the list and writes data in that cell. On the next render, React goes through the list and reads values respectively.

    Second Rule of Hooks

    Hooks can be called only from React Function Components and from Custom Hooks. If we tried to call a Hook from the Class Component we would get an error and wouldn’t be able to do so.

    Creating a Custom React Hook

    Let’s start with an example of what we’ve already learned. We will have one input field and add useEffect Hook that will run validation for that field. We will add a constraint that the username cannot contain a dollar sign.

    import React, {useState, useEffect} from 'react';function Example(props) {    const [username, setUsername] = useState('');    const [isValidUsername, setValidUsername] = useState(true);    useEffect(() => {        if(username.indexOf('$') !== -1) {            setValidUsername(false);        } else {            setValidUsername(true);        }    }, [username]);    function changeUsername(event) {        setUsername(event.currentTarget.value);    }    return (        <React.Fragment>            <input                 type="text"                value={username}                onChange={changeUsername}             />            {isValidUsername ?                <div>Username is valid</div>                :                <div>Username is invalid</div>            }        </React.Fragment>    );}export default Example;

    If we want to have the same validation for other input fields, we can extract the validation Part to a Custom Hook.

    1. Create a file useValidation.js inside src/hooks directory
    2. Create a function inside this file named useValidation and export it as default (Hooks are Functions, which means they can have parameters and a return value)
    3. Import this function inside the Example Component
    4. Add a parameter to the useValidation function named value
    5. Move Validation Logic from Example Component to useValidation Hook
    6. Rename username to value

    useValidation.js

    import {useState, useEffect} from 'react';function useValidation(value) {    const [isValid, setIsValid] = useState(true);    useEffect(() => {        if(value.indexOf('$') !== -1) {            setIsValid(false);        } else {            setIsValid(true);        }    }, [value]);    return isValid;}export default useValidation;

    Example.jsx

    import React, {useState} from 'react';import useValidation from '../../hooks/useValidation';function Example(props) {    const [username, setUsername] = useState('');    const isValidUsername = useValidation(username);    function changeUsername(event) {        setUsername(event.currentTarget.value);    }    return (        <React.Fragment>            <input                 type="text"                value={username}                onChange={changeUsername}             />            {isValidUsername ?                <div>Username is valid</div>                :                <div>Username is invalid</div>            }        </React.Fragment>    );}export default Example;

    Now we can have Validation logic inside multiple Components and also call it multiple times inside the same Component.

    React Hooks vs. Classes

    React Hooks provide a way to have some functionality that was only available in class Components before. React Hooks can’t completely replace classes, at least not yet. And, even if they do become able to replace classes, in the official React Documentation it states: “There are no plans to remove classes from React”. Hooks are backwards compatible, which means you can add them to your project without breaking functionality.

    With Hooks we are now able to have State inside Function Components. We can also hook into Component’s lifecycle using useEffect hook.

    The Lifecycle methods that have equivalents with Hooks are:

    • componentDidMount()
    • componentDidUpdate()
    • componentWillUnmount()
    • static getDerivedStateFromProps()

    The Lifecycle methods that don’t have equivalents with Hooks yet are:

    • componentDidCatch()
    • static getDerivedStateFromError()
    • getSnapshotBeforeUpdate()

    We can also use Context and Refs inside Function Components now. UseContext Hook is equivalent to Context Consumer from class Components. The syntax for useContext is simple and it doesn’t add nesting to your code. It makes code a lot cleaner.

    Conclusion

    Hooks are definitely worth giving a try. They reduce nesting and make your code cleaner. They also cut down development time if you have written a component as a function and realized afterwards you need state inside your component. You no longer need to rewrite the component as a class. You can simply use the state inside Function Component.

    Since Hooks are a new concept in React, some third-party libraries don’t have support for them yet. Even React-Redux is still in experimental phase with Hooks. But, I believe they will become a preferred way to write your components soon, so start learning them on time.

    kim-novak_authorsphoto.png
    Kim Novak Software Developer

    Loves React. Passionate Learner. Has the need to share her knowledge.