DEV Community

Enda
Enda

Posted on • Edited on

Controlling component visibility with React Hooks

With the release of React 16.8, Hooks are the newest addition to the world's most popular front-end framework. Hooks give us access to state and other lifecycle events from functional components. Before now functional components were always stateless. To use state we had to write class components, which are more verbose and complex than their functional counterparts.

In this guide we are going to make a simple component which will remain hidden until a button is clicked. We will also set a timeout event to hide the component after a set duration.

For this we will use two hooks - useState and useEffect. I'll explain what they do when we go to implement them, but for now let's define the component and its props.

export const Alert = ({ visible, duration, onDurationEnd, children }) => {
    return children;
};
Enter fullscreen mode Exit fullscreen mode

I've defined a functional component with four props.

  • visible (required) - This will be a boolean, either true or false. It controls the current visible state of the component.
  • duration (optional) - This is the duration is milliseconds that the component should display for before hiding again. If it is not set, the component will remain visible.
  • onDurationEnd (optional) - Callback function that executes after the duration has ended. Typically used reset the component's visibility to false from the parent component.
  • children (required) - This can be anything, from a single word to multiple components. children are added to the component as child elements.

State

useState allows us to use and set component state, which persists across renders. useState returns a pair, the current state value and a function for modifying it. Finally, useState takes a single argument which sets the initial value.

import React, { useState } from "react";

export const Alert = ({ visible, duration, onDurationEnd, children }) => {
    const [isVisible, setVisibility] = useState(null);

    return children;
};
Enter fullscreen mode Exit fullscreen mode

Here we have set up a hook to control the visibility of the component. The initial state value is null as this will be overwritten almost immediately after render.

What I really like about useState is you can declare it multiple times in a single component.

const [isVisible, setVisibility] = useState(null);
// defining a second state variable
const [message, setMessage] = useState(null);
Enter fullscreen mode Exit fullscreen mode

The benefit of this is that we can separate the control of different state values. In a class component, all state values are in a single object. So if you want to update one, you must also update the rest.

function setVisibility(visible) {
    this.setState({
        ...this.state,
        isVisible: visible
    });
}
Enter fullscreen mode Exit fullscreen mode

Effect

useEffect lets us perform side effect functions from a functional component. A side effect is something which affects something outside of the function being executed, like state or a network request. Think of useEffect like componentDidMount, componentDidUpdate and componentWillUpdate combined. By using this hook, you tell React to execute the logic inside the hook after every render.

export const Alert = ({ visible, duration, onDurationEnd, children }) => {
    const [isVisible, setVisibility] = useState(null);

    useEffect(() => {
        setVisibility(visible); // update the state
    }, [visible]); // hook is only triggered when value changes

    return children;
};
Enter fullscreen mode Exit fullscreen mode

So what does this do? After setting the initial state value, the useEffect hook is the next event to run. The hook overrides the initial value for isVisible to match the value acquired from the parent component's props.

The final argument in useEffect is an optional optimization. The effect will only re-run the if the value of visible changes, preventing unnecessary re-renders.

Once the useEffect hook has finished, we want to check the value of isVisible. If it's false, we don't want to render the component, so we return null.

if (!isVisible) return null;
Enter fullscreen mode Exit fullscreen mode

If duration contains a value, we need to set a timeout which will reset the component's visibility once the duration has passed. If onDurationEnd is defined then the parent component expects the value for controlling this component's visibility to also be reset to false, once the timeout has complete.

if (duration) {
    setTimeout(() => {
        setVisibility(false);

        // pass `false` back to the parent to update its state
        if (onDurationEnd) {
            onDurationEnd(false);
        }
    }, duration);
}
Enter fullscreen mode Exit fullscreen mode

Take a look at the finished component below. The introduction of React hooks has made developing components quicker, with fewer lifecyle events to worry about. The file itself is reduced by a number of lines versus a class component that does the same thing.

import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";

/**
 * Customisable alert component that remains hidden until called.
 *
 * @param {boolean} props.visible - The current visibility of the component.
 * @param {number} [props.duration] - The time in milliseconds to display the component for. If not set, the component will stay visible.
 * @param {func} onDurationEnd - Set visible state of component from parent.
 * @param {*} props.children - Child components.
 */
export const Alert = ({ visible, duration, onDurationEnd, children }) => {
    const [isVisible, setVisibility] = useState(null);

    useEffect(() => {
        setVisibility(visible);
    }, [visible]);

    if (!isVisible) return null;

    if (duration) {
        setTimeout(() => {
            setVisibility(false);

            if (onDurationEnd) {
                onDurationEnd(false);
            }
        }, duration);
    }

    return children;
};

Alert.propTypes = {
    visible: PropTypes.bool.isRequired,
    timeout: PropTypes.number,
    children: PropTypes.node.isRequired
};
Enter fullscreen mode Exit fullscreen mode

Check out a live demo or the code from this tutorial on GitHub.

If you like this post, check out the original and subscribe for more!

Top comments (0)