Create tooltip in react

Learn how to create a tooltip in react.

Tooltip is used to shows a dialog with info text when hovered over an element.

Tooltip in react

We are going to similar tooltip component as you can see in the above picture which will have different placement options.

For development we will need few extra packages.

  • prop-types: To validate the props.
  • classnames: With this we can use CSS classes as javascript object, we just need to name our CSS file as filename.module.css.
  • react-spring: Helps to add animation to the component while mounting and unmouting.

There are two ways in which we can create this component.
1. By creating a functional component which will generate the tooltip but the it style and toggle state will be controlled by CSS.

2. We create a stateful component and render the tooltip when it is active state. We have to maintain the state over here.

The second approach is much better because you can complete control the component, for example you can decide if the tooltip should be visible initially or not.

We are also going to create our component with the second approach.

Let us start the development by importing all the packages and setting the structure of the component.

import React, { Component } from "react";
import styles from "./index.module.css";
import PropTypes from "prop-types";
import cx from "classnames";
import { Spring } from "react-spring/renderprops";

class ToolTip extends Component {
  //Other code will go here.
}

export default ToolTip;

Validate the props, so that we can get good idea about how we should layout the elements.

  static propTypes = {
    text: PropTypes.string.isRequired,
    placement: PropTypes.oneOf(["top", "bottom", "left", "right"]).isRequired,
    active: PropTypes.bool.isRequired,
    classname: PropTypes.string,
    children: PropTypes.node
  };

  static defaultProps = {
    text: "",
    placement: "bottom",
    active: false
  };

Also set the default props if the required props are missing.

Now create the state to track the changes within the component.

  state = {
    active: this.props.active
  };

  show = () => {
    this.setState({
      active: true
    });
  };

  hide = () => {
    this.setState({
      active: false
    });
  };

This will not be controlled by the parent completely, it will maintain its own state to toggle the tooltip text. Props will be useful for initial rendering only.

Layout of tooltip.

As tooltip text are shown next to the parent, we will get the children and place it in a wrapper and create the tooltip text next to it.

   render() {
    const { text, placement, children, classname } = this.props;
    const { active } = this.state;

    return (
      <div
        className={cx(styles.tooltip, classname)}
        onMouseEnter={this.show}
        onMouseLeave={this.hide}
      >
        {children}
        {active && (
          <Spring from={{ opacity: 0 }} to={{ opacity: 1 }}>
            {props => (
              <span
                className={cx(styles.tooltiptext, styles[placement])}
                style={props}
              >
                {text}
              </span>
            )}
          </Spring>
        )}
      </div>
    );
  }

As we can use the CSS classes as objects, styles[placement] dynamically adds the respective class.

React does not have hover event listener, instead we are using onMouseEnter and onMouseLeave which are combined to use as hover.

onMouseEnter the active state changes to true and tooltip text is rendered and onMouseLeave vice versa happens.

Spring will animate the component while mounting and unmounting.

Complete code of tooltip component in react

import React, { Component } from "react";
import styles from "./index.module.css";
import PropTypes from "prop-types";
import cx from "classnames";
import { Spring } from "react-spring/renderprops";

class ToolTip extends Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
    placement: PropTypes.oneOf(["top", "bottom", "left", "right"]).isRequired,
    active: PropTypes.bool.isRequired,
    classname: PropTypes.string,
    children: PropTypes.node
  };

  static defaultProps = {
    text: "",
    placement: "bottom",
    active: false
  };

  state = {
    active: this.props.active
  };

  show = () => {
    this.setState({
      active: true
    });
  };

  hide = () => {
    this.setState({
      active: false
    });
  };

  render() {
    const { text, placement, children, classname } = this.props;
    const { active } = this.state;

    return (
      <div
        className={cx(styles.tooltip, classname)}
        onMouseEnter={this.show}
        onMouseLeave={this.hide}
      >
        {children}
        {active && (
          <Spring from={{ opacity: 0 }} to={{ opacity: 1 }}>
            {props => (
              <span
                className={cx(styles.tooltiptext, styles[placement])}
                style={props}
              >
                {text}
              </span>
            )}
          </Spring>
        )}
      </div>
    );
  }
}

export default ToolTip;

Complete style code of tooltip component

The style code are specific for a tooltip.

We have created a wrapper with relative position and then placed the tooltip text as an absolute child inside it.

Then we have provided different classes for the placement and the arrow direction.

import React, { Component } from "react";
import styles from "./index.module.css";
import PropTypes from "prop-types";
import cx from "classnames";
import { Spring } from "react-spring/renderprops";

class ToolTip extends Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
    placement: PropTypes.oneOf(["top", "bottom", "left", "right"]).isRequired,
    active: PropTypes.bool.isRequired,
    classname: PropTypes.string,
    children: PropTypes.node
  };

  static defaultProps = {
    text: "",
    placement: "bottom",
    active: false
  };

  state = {
    active: this.props.active
  };

  show = () => {
    this.setState({
      active: true
    });
  };

  hide = () => {
    this.setState({
      active: false
    });
  };

  render() {
    const { text, placement, children, classname } = this.props;
    const { active } = this.state;

    return (
      <div
        className={cx(styles.tooltip, classname)}
        onMouseEnter={this.show}
        onMouseLeave={this.hide}
      >
        {children}
        {active && (
          <Spring from={{ opacity: 0 }} to={{ opacity: 1 }}>
            {props => (
              <span
                className={cx(styles.tooltiptext, styles[placement])}
                style={props}
              >
                {text}
              </span>
            )}
          </Spring>
        )}
      </div>
    );
  }
}

export default ToolTip;

Input

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import ToolTip from "./Components/Tooltip";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <div className="abc">
    <ToolTip text="Left" placement={"left"} active={true}>
      <div style={{ padding: "5px", border: "2px solid", borderRadius: "4px" }}>
        ToolTip Left
      </div>
    </ToolTip>
    <ToolTip text="Right" placement={"right"} active={true}>
      <div style={{ padding: "5px", border: "2px solid", borderRadius: "4px" }}>
        ToolTip Right
      </div>
    </ToolTip>
    <ToolTip text="Top" placement={"top"} active={true}>
      <div style={{ padding: "5px", border: "2px solid", borderRadius: "4px" }}>
        ToolTip Top
      </div>
    </ToolTip>
    <ToolTip text="Bottom" placement={"bottom"} active={true}>
      <div style={{ padding: "5px", border: "2px solid", borderRadius: "4px" }}>
        ToolTip Bottom
      </div>
    </ToolTip>
  </div>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();