React, tooltips, for the little ones

What are tooltips?

Literally, the term “tooltips” itself is of English origin and is translated as “tips for tools”. According to Wikipedia, this term is understood as an information tooltip or a tooltip in which, when the mouse cursor is hovered over a screen element, information about this element is displayed in the text field, for example, a description of the button’s function, an abbreviation designation, etc.

The tooltip might look like this:

You have probably come across them more than once, so tooltips are a very common thing for web application interfaces.

React is a great framework for developing web applications. And many developers write on it, including me. And since a developer usually has a lot of projects, it would be good to reuse interface components in their projects, from one to another. For development speed, it’s cool to have a set of such universal components for the interface. In order for these components to support their basic functionality, they can be easily styled for a specific project and use third-party libraries to a minimum for their lightness.

Let’s do just that – write our own universal and easily reusable component for tooltips.

We will write in React + TypeScript, we will use css.modules for styling. In the future, for a smooth animation of the appearance and disappearance of tooltips, we will also connect the React library “react-transition-group”, but that’s later.

Now let’s look at what we want to do. Our future tooltip will need to accept an arbitrary UI element as a JSX React component, and when you hover over this target UI element, show a tooltip for it.

First, let’s create our new React component – ToolTipComponent

ToolTipComponent.tsx

import React from 'react';
import classes from './ToolTipComponent.module.css';

const ToolTipComponent: React.FC = () => {  
  return (    
    <div className={classes.container}>      
      ToolTip-Component    
    </div>  
  );
};

export default ToolTipComponent;

We will define its styling in the file – ToolTipComponent.module.css, for now it consists of only one class

ToolTipComponent.module.css

.container {  
  display: flex;
}

Paste our tooltip into the App component

import React from 'react';
import classes from './App.module.css';
import ToolTipComponent from './Components/ToolTipComponent/ToolTipComponent';

function App() {  
  return (    
    <div className={classes.container}>      
      <ToolTipComponent />    
    </div>  
  );
}

export default App;

Our application now looks like this:

In order for us to display the target component on hover over which we want to see a tooltip inside the ToolTipComponent component, we must pass this target component as props – children to our tooltip.

We also need text for the tooltip itself, so we’ll pass it as props – text. Inside the component, wrap the tooltip text in a tag to style it and position it relative to its parent container.

Now our ToolTipComponent looks like this:

ToolTipComponent.tsx

import React, { ReactElement } from 'react';
import classes from './ToolTipComponent.module.css';

type PropsType = {
  children: ReactElement;
  text: string;
};

const ToolTipComponent: React.FC  <PropsType>= ({children, text}) => {
  return (
    <div className={classes.container}>
      {children}
      <div className={classes.tooltip}>
        {text}
      </div>
    </div>
  );
};

export default ToolTipComponent;

ToolTipComponent.module.css

.container {
  position: relative;
  display: flex;
}

.tooltip {
  position: absolute;
  width: 180px;
  padding: 4px 12px;
  margin-left: calc(100%);
  justify-content: center;
  color: #FFFFFF;
  background-color: #FF8E00;
  border-radius: 12px ;
  text-align: center;
  white-space: pre-line;
  font-weight: 700 ;
  pointer-events: none;
}

Let’s create and style the component an arbitrary component. For example, I created a ButtonComponent. This will be the most ordinary button made from a tag, our button will not do anything. But this will be the target component that we wrap in our tooltip so that when we hover over it we can see our tooltip.

ButtonComponent.tsx

import React from 'react';
import classes from './ButtonComponent.module.css';

const ButtonComponent: React.FC = () => {  
  return (    
    <div className={classes.container}>      
      Нажми на меня    
    </div>  
  );
};

export default ButtonComponent;

And the styling of the button is written here:

ButtonComponent.module.css

.container {  
  display: flex;  
  width: 80px;  
  justify-content: center;  
  align-items: center;  
  padding: 6px 24px;  
  text-align: center;  
  color: #FFFFFF;  
  background-color: #4A90E2;  
  font-size: 18px;  
  font-weight: 600;  
  border-radius: 12px;  
  cursor: pointer;
}

The screen of our application now looks like this – a button and our tooltip is positioned next to it.

We will control the behavior of our tooltip through the state of React using the hook – useState. At the moments when the mouse pointer enters the tooltip component, we will display a tooltip. As soon as the mouse pointer leaves the tooltip component, we will hide the tooltip. Why use the mouse events for the React component – “onMouseEnter” and “onMouseLeave”.

Now our TooTtipComponent looks like this

ToolTipComponent.tsx

import React, { ReactElement, useState } from 'react';
import classes from './ToolTipComponent.module.css';

type PropsType = {
  children: ReactElement;
  text: string;
};

const ToolTipComponent: React.FC<PropsType> = ({ children, text }) => {
  const [showToolTip, setShowToolTip] = useState(false);

  const onMouseEnterHandler = () => {
    setShowToolTip(true);
  };

  const onMouseLeaveHandler = () => {
    setShowToolTip(false);
  };

  return (
    <div className={classes.container} onMouseEnter={onMouseEnterHandler} onMouseLeave={onMouseLeaveHandler}>
      {children}
      {showToolTip && <div className={classes.tooltip}>{text}</div>}
    </div>
  );
};

export default ToolTipComponent;

and now our tooltip can already dynamically show a tooltip for our button.

However, you can see that the tooltip appears instantly as soon as the mouse pointer is over the target component. This is not good in terms of user experience because the user has already interacted with this button before and knows what it is used for. In this case, the user does not need a hint, he just wants to click on the button. Let’s give the user this option.

Let’s fix our tooltip so that there is a delay of 0.75 seconds before the tooltip appears. During these 0.75 seconds, the user will have time to click on the button and no hint will appear for him.

To implement this functionality, we use the setTimeout timer method from the browser API. When the mouse pointer enters the target component, a function will be launched that delays the display of the tooltip text, and if the mouse pointer leaves the target component, we will reset the setTimeout timer and hide the tooltip text. In order not to lose the setTimeOut ID between re-renders of our ToolTipComponrent React component, we will put the value of the setTimeout method ID in the current field of the React – useRef hook. The current field of the useRef hook is a generic storage in React components for the data we want to persist between their re-renders.

And now our code for our tooltip component looks like this

ToolTipComponent.tsx

import React, { ReactElement, useRef, useState } from 'react';
import classes from './ToolTipComponent.module.css';

type PropsType = {
  children: ReactElement;
  text: string;
};

const ToolTipComponent: React.FC<PropsType> = ({ children, text }) => {
  const refSetTimeout = useRef<NodeJS.Timeout>();
  const [showToolTip, setShowToolTip] = useState(false);

  const onMouseEnterHandler = () => {
    refSetTimeout.current = setTimeout(() => {
      setShowToolTip(true);
    }, 750);
  };

  const onMouseLeaveHandler = () => {
    clearTimeout(refSetTimeout.current);
    setShowToolTip(false);
  };

  return (
    <div className={classes.container} onMouseEnter={onMouseEnterHandler} onMouseLeave={onMouseLeaveHandler}>
      {children}
      {showToolTip && <div className={classes.tooltip}>{text}</div>}
    </div>
  );
};

export default ToolTipComponent;

As you can see in the video, our idea of ​​a 0.75 second delay in the appearance of the tooltip text works. The tooltip appears only after some time, after the user hovered the mouse over the button. If he clicked on the button or moved the pointer outside the button faster than our delay of 0.75 seconds, then the tooltip for him would not appear.

But in order for our tooltip component to be universal and we can reuse it in different projects, we need to add the ability to style the tooltip. Let’s do this by passing one more props – customClass to our component.

And now our tooltips component code looks like this

ToolTipComponent.tsx

import React, { ReactElement, useRef, useState } from 'react';
import classes from './ToolTipComponent.module.css';

type PropsType = {
  children: ReactElement;
  text: string;
  customClass?: string;
};

const ToolTipComponent: React.FC<PropsType> = ({ children, text, customClass }) => {
  const refSetTimeout = useRef<NodeJS.Timeout>();
  const [showToolTip, setShowToolTip] = useState(false);
  const toolTipClasses = customClass ? `${classes.tooltip} ${customClass}` : `${classes.tooltip}`;

  const onMouseEnterHandler = () => {
    refSetTimeout.current = setTimeout(() => {
      setShowToolTip(true);
    }, 750);
  };

  const onMouseLeaveHandler = () => {
    clearTimeout(refSetTimeout.current);
    setShowToolTip(false);
  };

  return (
    <div className={classes.container} onMouseEnter={onMouseEnterHandler} onMouseLeave={onMouseLeaveHandler}>
      {children}
      {showToolTip && <div className={toolTipClasses}>{text}</div>}
    </div>
  );
};

export default ToolTipComponent;

Connect .customStyle – passing it in props to our ToolTipComponent

App.tsx

import React from 'react';
import classes from './App.module.css';
import ToolTipComponent from './Components/ToolTipComponent/ToolTipComponent';
import ButtonComponent from './Components/ButtonComponent/ButtonComponent';

function App() {
  return (
    <div className={classes.container}>
      <ToolTipComponent text={'Я подсказка'} customClass={classes.toolTipCustom}>
        <ButtonComponent />
      </ToolTipComponent>
    </div>
  );
}

export default App;

The .customClass for our new tooltip style looks like this

App.module.css

.container {
  display: flex;
  padding: 64px;
}

.toolTipCustom {
  display: flex;
  top: -5px;
  left: 32px;
  height: 48px;
  padding: 8px 32px;
  align-items: center;
  background-color: #1e9f00;
  color: #fdfa65;
  border-radius: 50%;
}

Here’s what our tooltip looks like now. It is styled and positioned exactly as we described it in the .toolTipCustom css class.

It would seem that this is all, we have achieved what we wanted – We have a universal component for displaying tooltips. We can wrap any element of our interface into this component, pass the text of the tooltip, styles for it to it, and get our tooltip when you hover the mouse over this element.

And this is quite a working component, but it is not difficult to improve it further.

Using the “react-transition-group” library provided by the React team, we’ll add to our tooltip the ability to fade in, slide out of the way, and then fade out. And to give all these extended properties to our tooltip, we use the “CSSTransition” component from the “react-transition-group” library

First, install the library itself from npm and then include “CSSTransition” in our tooltip component. Notice that instead of the condition we previously allowed the tooltip to render, now the imported “CSSTransition” component will take over the responsibility of controlling the rendering of our tooltip, and will also apply styling to the animation we are now implementing.

ToolTipComponent.tsx

import React, { ReactElement, useRef, useState } from 'react';
import classes from './ToolTipComponent.module.css';
import { CSSTransition } from 'react-transition-group';

type PropsType = {
  children: ReactElement;
  text: string;
  customClass?: string;
};

const transitionClasses = {
  enter: classes.exampleEnter,
  enterActive: classes.exampleEnterActive,
  exit: classes.exampleExit,
  exitActive: classes.exampleExitActive,
};

const ToolTipComponent: React.FC<PropsType> = ({ children, text, customClass }) => {
  const refSetTimeout = useRef<NodeJS.Timeout>();
  const [showToolTip, setShowToolTip] = useState(false);
  const toolTipClasses = customClass ? `${classes.tooltip} ${customClass}` : `${classes.tooltip}`;


  const onMouseEnterHandler = () => {
    refSetTimeout.current = setTimeout(() => {
      setShowToolTip(true);
    }, 750);
  };

  const onMouseLeaveHandler = () => {
    clearTimeout(refSetTimeout.current);
    setShowToolTip(false);
  };

  return (
    <div className={classes.container} onMouseEnter={onMouseEnterHandler} onMouseLeave={onMouseLeaveHandler}>
      {children}
      <CSSTransition in={showToolTip} timeout={750} classNames={transitionClasses} unmountOnExit>
        <div className={toolTipClasses}>{text}</div>
      </CSSTransition>
    </div>
  );
};

export default ToolTipComponent;

To style the animation of the “CSSTransition” component, its 4 internal classes are used:

  • enter – how the component looks at the beginning of the entry animation

  • enterActive – how the component looks during the entry animation

  • exit – how the component looks at the end of the disappear animation

  • exitActive – how the component looks during the disappear animation

Let’s set in css classes for our tooltip its smooth appearance through the “opacity,” property and the same smooth descent from above through transform: translateY().

ToolTipComponent.module.css

.container {
  position: relative;
  display: flex;
}

.tooltip {
  position: absolute;
  width: 180px;
  padding: 4px 12px;
  margin-left: calc(100%);
  justify-content: center;
  color: #FFFFFF;
  background-color: #FF8E00;
  border-radius: 12px ;
  text-align: center;
  white-space: pre-line;
  font-weight: 700 ;
  pointer-events: none;
}

.exampleEnter {
  opacity: 0;
  transform:translateY(-100%);
}
.exampleEnterActive {
  opacity: 1;
  transform:translateY(0);
  transition: opacity 350ms, transform 350ms;
}
.exampleExit {
  opacity: 1;
  transform:translateY(0);
}
.exampleExitActive {
  opacity: 0;
  transform:translateY(-100%);
  transition: opacity 350ms, transform 350ms;
}

And now the final version of our pop-up, or rather already smoothly descending from above and just as smoothly moving down, tips on the user’s screen looks like this.

Playing around with the styles of the “.toolTipCustom” class in the file App.module.css you can easily customize the appearance of your tooltip. And after playing with the styles “.exampleEnter”, “.exampleEnterActive”, “.exampleExit”, “.exampleExitActive” in the file ToolTipComponent.module.cssyou can easily customize any animation of the appearance and disappearance of your hint. You can change the delay time, animation time, fade in and fade out sides, colors, sizes, fonts and use all the styling power through css classes that modern browsers provide us.

You are free to experiment with the code in this article and use it to your advantage and projects.

The code in the article is available on GitHub https://github.com/alexeyk500/ToolTipComponent

PS – The article was written in memory of the cool work in 2021-2023 at the “E-ngineers” company, with colleagues from whom I learned a lot in these two years!!!

Sincerely, Alexeyk500.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *