Introduction To Custom React Hooks

An introduction to the recently introduced Custom Hooks by React and how they can be used for an elevated DevOps experience

Introduction To Custom React Hooks

Although introduced quite recently, Hooks have fit into React development processes seamlessly. They are functions that "hook" into existing components or modules to provide us with logical and reusable features. In the terms of React, they are built-in functions that allow developers to use state and lifecycle methods inside functional components.

Some of the most common Hooks provided by React are useEffect, useState, useContext, useRef, useMemo, etc. Let's take a look at a few of them and see how can they fit into our components seamlessly.

React Hooks

useState Hook

This Hook allows us to set the state in functional components by returning an array of two elements: the first being the state variable and the second being the function to modify it. We can also initialize our state value by sending an argument in the Hook call.

useEffect Hook

This Hook adds the ability to perform side effects in a functional component which includes operations like data fetching, attaching and detaching event listeners, etc.

The following code is an example of you can set the document title when we try to increase the count by clicking on a button:

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

By default, React runs a "side effect" after every render but we can modify it according to our own needs. This is possible with the help of a dependency array. If our variables added in the dependency array change, only then will the side effect runs.

Custom Hooks

The React community has done a great job in providing us with Hooks that cover almost any logic we can think of. However, there are cases wherein you can't implement them on their own because multiple components may have similar logic.

Let's say we have an application that has different components which can edit a user's information in which there is one component in the Setting's section and the other in the Home section. We would have the same functions for handling the API call and updating the state for both of them. What about the details of user which aren't shown in either of these sections and are still editable? You can use the same API to edit those details.

This is just one entity and it has already led us to write the same piece of code at three places in our application. Here is where Custom Hooks come in to save the day! A Custom Hook by definition is a JavaScript function whose name starts with use and this component will call other React Hooks to extract logic from multiple components and make them reusable.

Some use cases for them are:

Form validation:

Let's take two simple input fields: Email and Password.

  • These will be initialized along with their validation boolean states in the useValidation. The Hook consists of a useEffect which is what one that handles the validation process and each time the email or password is changed, the side effect will run. The value and onChange properties are being handled by the Hook; this way, other components can reuse this logic to validate their authentication forms along with their fields.
  • Instead of making specific state variables, you can also send parameters to the Hook stating the number of input fields you want, their names and types. This may need some additional conditions and tinkering alongwith the logic for validation but it surely becomes more reusable. Refer to the code given below to proceed:

const useValidation = () => {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [isEmailValid, setIsEmailValid] = useState(false);
    const [isPasswordValid, setIsPasswordValid] = useState(false);

    const emailRegex =
        /^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/;
    const passwordRegex =
        /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/;

    useEffect(() => {
        setEmail(email);
        setPassword(password);
        if (email.match(emailRegex)) {
            setIsEmailValid(true);
        } else {
            setIsEmailValid(false);
        }
        if (password.match(passwordRegex)) {
            setIsPasswordValid(true);
        } else {
            setIsPasswordValid(false);
        }
    }, [email, password]);

    return {
        isEmailValid,
        isPasswordValid,
        email,
        password,
        setEmail,
        setPassword,
    };
};

//component
export default function App() {

    //calling the hook
    const {
        isEmailValid,
        isPasswordValid,
        email,
        password,
        setEmail,
        setPassword,
    } = useValidation();

    const handleSubmit = () => {
        //code for submitting code
    };
    return (
        <div className='App'>
            <h2>Email</h2>
            <input value={email} onChange={(e) => setEmail(e.target.value)} />
            <span>{isEmailValid ? "Valid" : "Invalid"}</span>
            <h2>Pasword</h2>
            <input
                value={password}
                type='password'
                onChange={(e) => setPassword(e.target.value)}
                autoComplete={false}
            />
            <span>{isPasswordValid ? "Valid" : "Invalid"}</span>
            <button
                type='submit'
                onClick={handleSubmit}
                disabled={!(isEmailValid && isPasswordValid)}
                autoComplete={false}
            >
                Submit
            </button>
        </div>
    );
}

I wrote the code snipped given above with complete logic so that you can understand that these Hooks are really simple. You don't have to worry about the complexity of the code, our next few examples would only help us with the code flow rather than with the functionality.

So, besides field validation what other component logics are very common in React?

Fetching data/ making an API call

In this example, we're going to create a Hook which will make an API call for us. It will have three state variables: loading, data and error. These would be updated accordingly when we call this Hook in the required component and send a url as an argument. This way any component can make an API call using the same Hook and we wouldn't have to worry about redundancy. Refer to the code given below:

const useFetch = (url) => {
    const [loading, setLoading] = useState(true);
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);

    const fetchApi = (url) => {
        fetch(url)
            .then((response) => {
                return response.json();
            })
            .then((json) => {
                setData(json);
            })
            .catch(() => {
                setError(error);
            })
            .finally(() => {
                setLoading(false);
            });
    };

    useEffect(() => {
        fetchApi();
    }, [url]);

    return { loading, data, error };
};

//component
export default function App() {
    //calling the hook
    const { data, loading, error } = useFetch("https://api-call");

    if (loading) return <Loading />;

    return (
        <div className='App'>
            {data ? JSON.stringify(data) : JSON.stringify(error)}
        </div>
    );
}

Authentication

Our Custom Hook, useAuth consists of authentication methods like signin, signup, and signout. Additionally we can add methods for changing the password, forgot password functionality etc. The crux is that the user state in our Hook would store the user's details and would send it to other components along with the required authentication methods. This way you can call these functions from anywhere in the application and we are also provided with an an Event Listener in the useEffect for re-authentication. Refer to the code given below:

const useAuth = () => {
    const [user, setUser] = useState(null);

    const signin = (email, password) => {
        // code for signing and setting the user
    };

    const signup = (email, password) => {
        // code for registering an account and setting the user
    };

    const signout = () => {
        // code for signing out and setting the user
    };

    return {
        user,
        signin,
        signup,
        signout,
    };
};

export default function App() {
    const { signin, user } = useAuth();

    if (!user) {
        signin();
    }

    return <div>{user}</div>;
}

Pro Tips

These are some of the best practices you can follow which would help you make pro level Custom Hooks:

  • First and foremost, follow the rules of Hooks.
  • The React team has built an ESList plugin called eslint-plugin-react-hooks for helping developers write Custom Hooks efficiently.
  • Follow the React component structure diligently in order of state, function and render.
  • Use memoization and useCallback Hooks for reducing operational time.

To sum it up...

These are just few use cases which we have seen in detail. Besides these, you can create them for a variety of use cases. Some of these include:

  • useToggle for storing logic of a boolean state value
  • useRouter for handling all routing related logic
  • useEventListener for attaching event listeners to components
  • useDarkMode for adding themes to your application which can be accessed from anywhere in the application
  • useAnimation for animation related methods
  • useHistory if you want to store a history of actions in the application
  • useLocalStorage for handling actions regarding storage

As you can see, one can find multiple use cases for Custom Hooks. The ones mentioned above are some of the most common ones. You can also find more use cases for them on the internet or create your own Hooks!