React-React hint: Tooltip component for React, Preact, Inferno

React-hint

npm package npm package

React-hint is a small tooltip component for React which is developed with simplicity and performance in mind. It also plays nicely with Preact and Inferno.

react-hint tooltip custom tooltip

How to install

npm i -S react-hint

How to import

// React
import React from 'react'
import ReactHintFactory from 'react-hint'
const ReactHint = ReactHintFactory(React)

// Preact
import {h, Component} from 'preact'
import ReactHintFactory from 'react-hint'
const ReactHint = ReactHintFactory({createElement: h, Component})

// Inferno
import Inferno from 'inferno-compat'
import ReactHintFactory from 'react-hint'
const ReactHint = ReactHintFactory(Inferno)

// UMD
const ReactHint = window.ReactHintFactory(window.React)

You don't need to include ReactHint in every component which uses tooltips, just include it once in the topmost container component.

Use

ReactHint is (in 99% of cases) a singleton-component which is used to render tooltips which appear on multiple elements :

<ReactHint autoPosition events />
<button data-rh="tooltip 1">Hover me 1 !</button>
<button data-rh="tooltip 2">Hover me 2 !</button>
<button data-rh="tooltip 3">Hover me 3 !</button>
<button data-rh="tooltip 4">Hover me 4 !</button>

The text content which appears inside the tooltip is set by data-rh attribute.

Tooltip will appear on hover on every element with data-rh attribute.

Note : tooltip can also be toggled by calling toggleHint() on the ref of a the component:

<ReactHint autoPosition events ref={(ref) => { this.tooltip = ref; }} />
<div data-rh="tooltip">Element with tooltip</div>
<button onClick={() => { this.tooltip.toggleHint(); }}>Click me to toggle React Hint on element !</button>

custom content

In case you need to define custom content (HTML), you must use the onRenderContent prop of ReactHint:

<ReactHint
	autoPosition
	events
	onRenderContent={(target) => (<div><p>`tooltip ${target.number}`</p></div>)}
/>
<button data-rh data-number="1">Hover me 1 !</button>
<button data-rh data-number="2">Hover me 2 !</button>
<button data-rh data-number="3">Hover me 3 !</button>
<button data-rh data-number="4">Hover me 4 !</button>

Use data-abcdef attribute on component which uses tooltip to pass data which can be accessed via target.abcdef in onRenderContent().

ReactHint is not your regular wrapping tooltip component library, e.g. this is incorrect:

<ReactHint>
	<div>Content of the tooltip</div>
</ReactHint>

multiple instances

In case you need to define multiple instances of ReactHint (ex to show tooltips with different content layout), you can customize the attribute name per instance. ReactHint also supports custom tooltip content with attached event handlers by overriding the content renderer and returning a react node.

// default tooltip
<ReactHint autoPosition events />

// custom tooltip 1
<ReactHint
	persist
	attribute="data-custom-1"
	events={{click: true}}
	onRenderContent={(target) => (<div><p>`tooltip ${target.number}`</p></div>)}
/>

// custom tooltip 2
<ReactHint
	persist
	attribute="data-custom-2"
	events={{click: true}}
	onRenderContent={(target) => (<h1>`tooltip ${target.title}`</h1>)}
/>

<button data-rh="default tooltip 1">Hover me 1 to show default tooltip !</button>
<button data-rh="default tooltip 2">Hover me 2 to show default tooltip !</button>

<button data-custom-1	data-custom-1-number="123">Hover me to show custom tooltip 1 !</button>
<button data-custom-1	data-custom-1-number="456">Hover me to show custom tooltip 1 !</button>

<button data-custom-2	data-custom-2-title="Hello">Hover me to show custom tooltip 2 !</button>
<button data-custom-2	data-custom-2-title="World">Hover me to show custom tooltip 2 !</button>

Note : when using custom attribute name, data should be passed prefixed with attribute name as shown above.

Options

ReactHint Property Type Default Value Description
attribute String "data-rh" Allows setting a custom tooltip attribute instead of the default one.
autoPosition Boolean false Autopositions tooltips based on closeness to window borders.
className String "react-hint" You can override the tooltip style by passing the className property.
delay Number or {show: Number, hide: Number} 0 The default delay (in milliseconds) before showing/hiding the tooltip.
events Boolean or {click: Boolean, focus: Boolean, hover: Boolean} false Enables/disables all events or a subset of events.
onRenderContent Function Allows rendering of custom HTML content (with attached event handlers). Pass a function which returns a react node.
persist Boolean false Hide the tooltip only on outside click, hover, etc.
position "top", "left", "right", "bottom" "top" Allows setting the default tooltip placement.
ref Function You can pass a function which will get a reference to the tooltip instance.
DOM Element Attribute Type Default Value Description
data-rh String Sets the tooltip's text content (if onRenderContent is not used to set custom HTML content).
data-rh-at "top", "left", "right", "bottom" "top" Allows overriding the default tooltip placement.

Full Example

import React from 'react'
import {render} from 'react-dom'
import ReactHintFactory from 'react-hint'
import 'react-hint/css/index.css'

const ReactHint = ReactHintFactory(React)
class App extends React.Component {
	onRenderContent = (target, content) => {
		const {catId} = target.dataset
		const width = 240
		const url = `https://images.pexels.com/photos/${catId}/pexels-photo-${catId}.jpeg?w=${width}`

		return <div className="custom-hint__content">
			<img src={url} width={width} />
			<button ref={(ref) => ref && ref.focus()}
				onClick={() => this.instance.toggleHint()}>Ok</button>
		</div>
	}

	render() {
		return <div>
			<ReactHint autoPosition events delay={{show: 100, hide: 1000}} />
			<ReactHint persist
				attribute="data-custom"
				className="custom-hint"
				events={{click: true}}
				onRenderContent={this.onRenderContent}
				ref={(ref) => this.instance = ref} />

			<button data-rh="Default">Default</button>
			<button data-rh="Top" data-rh-at="top">Top</button>
			<button data-rh="Right" data-rh-at="right">Right</button>
			<button data-rh="Bottom" data-rh-at="bottom">Bottom</button>
			<button data-rh="Left" data-rh-at="left">Left</button>

			<button data-custom
				data-custom-at="bottom"
				data-cat-id="10913">Click Me</button>

			<button data-custom
				data-custom-at="bottom"
				data-cat-id="416088">Click Me</button>
		</div>
	}
}

render(<App />, demo)

License

MIT

Comments

  • [Feature-request] Fade out animation on leave
    [Feature-request] Fade out animation on leave

    Dec 14, 2018

    Currently it is not possible AFAIK to add a CSS animation which triggers once the element that toggle react-hint is hovered out.

    We can use delay={{ hide: someDelay }} props but then the react-hint is removed from DOM after specified delay when state.target is emptied, thus no animation can be shown.

    An option to allow fadeOut on leave would be to add a class once triggering element is hovered out (ex. .react-hint--hide) and use this class to set a fadeout animation similar to the CSS animation fadeIn. This fadeOut could complete as long as the animation-delay (0.5s by default on the fadeIn anim) is smaller than the hide delay set in delay props.

    enhancement help wanted 
    Reply
  •  [Feature-request] Fade in animation for multiple react-hint
    [Feature-request] Fade in animation for multiple react-hint

    Dec 14, 2018

    When using a react-hint which can be triggered by multiple elements (more than one element with data-rh attribute) and using a hide delay (delay={ hide: someDelay }), the fadeIn animation is only triggered when the first react-hint is shown (hover on one of the elements which toggles it). But if hovering onto another element before delay expires the animation does not kick in, indeed the react-hint DOM node is not removed and re-added but its position is changed (I suppose), so the animation is not triggered.

    An option to avoid that would be to add a class to the react-hint when a triggering element is hovered (ex. react-hint--show) and set the CSS animation fadeIn there. Let me know if your're ok with this option so I can work on a PR.

    enhancement help wanted 
    Reply
  • fade in animation also on tooltip change
    fade in animation also on tooltip change

    Dec 18, 2018

    Allow fadeIn animation while hovering from on element which toggles tooltip to another one (which uses the same ReactHint component) instead of displaying tooltip on the new element straight away.

    Turned out to be slightly more complicated than I thought (I had to use 2 different CSS classes to get desired effect), let me know if you think of a better solution.

    fix issue #35

    Reply
  • Close Hint On Element Click
    Close Hint On Element Click

    Aug 20, 2019

    If only the "click" event is set to true, it seems as though clicking an element with data-rh works to open the hint but clicking it again does not close the hint.

    I can wire up toggleHint from the ref, but it seems like the default here should be to close hint if the element is clicked again. If persist is set to true then it shouldn't close on click. At least that's how I interpret it.

    enhancement question v4 
    Reply
  • Discuss API v4
    Discuss API v4

    Aug 21, 2019

    As I am slooooooowly working on the next API, I guess it makes sense to gather all the feedback and ideas, and feature requests before it gets cut in stone. I am going to write down some of my thoughts about the new API and show what I have in mind.

    Folks, if you have major pain points with the current API, I would like to know about them and discuss what could be improved.

    help wanted v4 
    Reply
  • [Bug] Tooltips Do Not Work With ShadowDOMs
    [Bug] Tooltips Do Not Work With ShadowDOMs

    Jan 22, 2020

    Heyo,

    My preact application uses a shadow dom as a mounting point, and tooltips are not appearing. I did some digging through the source code, and I believe I know why.

    // ReactHint#toggleEvents, line 30
    ;(click || hasEvents) && document[action]('click', this.toggleHint)
    ;(focus || hasEvents) && document[action]('focusin', this.toggleHint)
    ;(hover || hasEvents) && document[action]('mouseover', this.toggleHint)
    ;(click || hover || hasEvents) && document[action]('touchend', this.toggleHint)
    

    Events in the shadow dom are not propagated up to document, so toggleHint never fires.

    One way that this could be fixed is by allowing users to specify the element where the ReactHint singleton should listen for events

    bug 
    Reply
  • Infinite render loop when tooltip is on the right edge of the page
    Infinite render loop when tooltip is on the right edge of the page

    Sep 1, 2021

    CodeSandbox here

    import ReactHintFactory from "react-hint";
    const ReactHint = ReactHintFactory(React);
    
    export default function App() {
      return (
        <div>
          <ReactHint events />
          <div
            style={{
              minHeight: "100vh"
            }}
          >
            <div
              style={{
                padding: "1rem",
                backgroundColor: "hsl(0, 0%, 92%)",
                display: "flex",
                justifyContent: "flex-end"
              }}
            >
              <button data-rh="Add top-level category">OK</button>
            </div>
          </div>
        </div>
      );
    }
    

    Everything in this example besides the backgroundColor is necessary to reproduce the bug.

    Error message when hovering over the button:

    Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
        at checkForNestedUpdates (react-dom.development.js:23803)
        at scheduleUpdateOnFiber (react-dom.development.js:21835)
        at Object.enqueueSetState (react-dom.development.js:12467)
        at ReactHint.Component.setState (react.development.js:365)
        at ReactHint.eval [as getHintData] (index.js:213)
        at ReactHint.componentDidUpdate (index.js:232)
        at commitLifeCycles (react-dom.development.js:20684)
        at commitLayoutEffects (react-dom.development.js:23426)
        at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
        at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
        at invokeGuardedCallback (react-dom.development.js:4056)
        at commitRootImpl (react-dom.development.js:23151)
        at unstable_runWithPriority (scheduler.development.js:468)
        at runWithPriority$1 (react-dom.development.js:11276)
        at commitRoot (react-dom.development.js:22990)
        at performSyncWorkOnRoot (react-dom.development.js:22329)
        at eval (react-dom.development.js:11327)
        at unstable_runWithPriority (scheduler.development.js:468)
        at runWithPriority$1 (react-dom.development.js:11276)
        at flushSyncCallbackQueueImpl (react-dom.development.js:11322)
        at flushSyncCallbackQueue (react-dom.development.js:11309)
        at scheduleUpdateOnFiber (react-dom.development.js:21893)
        at Object.enqueueSetState (react-dom.development.js:12467)
        at ReactHint.Component.setState (react.development.js:365)
        at ReactHint.eval [as getHintData] (index.js:213)
        at eval (index.js:77)
    
    
    The above error occurred in the <ReactHint> component:
    
        at ReactHint (https://1z08p.csb.app/node_modules/react-hint/lib/index.js:30:31)
        at div
        at App
    

    I will look into fixing it myself but help would definitely be appreciated!

    Reply
  • Window edge detection fails
    Window edge detection fails

    Sep 30, 2021

    When tooltip shows to top, it do not check, that it's content visible on screen:

    image

    Reply
  • Not working with Preact
    Not working with Preact

    Oct 2, 2021

    I am trying to use this in a Preact app, but can't get it to work.

    TypeError: createRef is not a function
    

    I have compat aliases set up in snowpack config:

    alias: {
        react: 'preact/compat',
        'react-dom': 'preact/compat',
      }
    

    and typescript paths in tsconfig.json:

    "paths": {
          "react": ["node_modules/preact/compat/"],
          "react-dom": ["node_modules/preact/compat/"]
        }
    

    I created the factory as recommended in the docs, but still get the above mentioned error.

    Has anyone else successfully used react-hint with the latest version of preact? (v10.5.14 at time of writing).

    Any ideas what I could do?

    Many thanks, Brian

    Reply
  • validateDOMNesting(...): <div> cannot appear as a child of <tr>.  at div     at TitleCell
    validateDOMNesting(...):
    cannot appear as a child of . at div at TitleCell

    Jan 5, 2022

    can anyone please help me with this, Thanks ..!!

    in Title cell components before I implement my code, it's working fine with this code

    import React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; import Breadcrumb from '../../../components/Breadcrumb';

    const StyledTitle = styled.divcolor: #091219; font-size: 1.125rem; font-weight: Bold; width: max-content; max-width: 250px; white-space: pre-line;; const TypeRow = styled.divdisplay: flex; justify-content: space-between;; const TypeHeader = styled.h4margin-bottom: 0px;;

    const TitleCell = ({ type, title, versionId }) => ( {type} <Breadcrumb from="titlecell" versionId={versionId} /> {title} );

    TitleCell.propTypes = { type: PropTypes.string.isRequired, title: PropTypes.string.isRequired, versionId: PropTypes.string.isRequired, };

    export default TitleCell;

    but I need to add tag and add href into this so changed this code to

    import React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; import { useStore } from 'react-redux'; import { useQuery } from '@apollo/client'; import { Button } from '@twigeducation/ts-fe-components'; import Breadcrumb from '../../../components/Breadcrumb'; import { breadcrumbsQuery } from '../../../components/Breadcrumb/Breadcrumbs.query';

    const StyledTitle = styled.divcolor: #091219; font-size: 1.125rem; font-weight: Bold; width: max-content; max-width: 250px; white-space: pre-line;; const TypeRow = styled.divdisplay: flex; justify-content: space-between;; const TypeHeader = styled.h4margin-bottom: 0px;;

    const BreadcrumbLink = styled.atext-decoration: inherit; color: inherit; cursor: pointer; &:visited{ text-decoration: inherit; color: inherit; cursor: pointer; } &:hover{ text-decoration: inherit; color: inherit; cursor: pointer; };

    const TitleCell = ({ type, title, versionId }) => { const { subscriptions } = useStore().getState(); const { loading, data, error } = useQuery(breadcrumbsQuery, { variables: { applicationId: subscriptions?.product?.tocsApp, versionId, }, });

    if (loading) {
        return <div data-at="loading" />;
    }
    
    if (error || !data.studentSession) {
        return null;
    }
    return (
        <table>
            <tbody>
                <td>
                    <BreadcrumbLink
                        href={`/student-session/${data.studentSession.attributes.slug}/${btoa(
                            versionId,
                        )}`}
                    >
                        <TypeRow>
                            <TypeHeader data-at="title-type-cell">{type}</TypeHeader>
                            <Breadcrumb from="titlecell" versionId={versionId} />
                        </TypeRow>
                        <StyledTitle data-at="title-header-cell">{title}</StyledTitle>
                    </BreadcrumbLink>
                </td>
            </tbody>
        </table>
    
    );
    

    };

    TitleCell.propTypes = { type: PropTypes.string.isRequired, title: PropTypes.string.isRequired, versionId: PropTypes.string.isRequired, };

    export default TitleCell;

    and it gave me errors on both sides ln browser console or in react test part. i just need to add a tag in top.

    Reply
  • Can only use one custom className
    Can only use one custom className

    Mar 12, 2018

    https://github.com/slmgc/react-hint/blob/30366904525aa951553564ea97496e156312b0ad/lib/index.js#L161

    Hi! It would be incredibly helpful if the ability to add more than one custom classname was introduced (perhaps a wrapper class for the outer div).

    I am trying to create a tooltip with two different colour settings. I can't find a way for me to do this at the moment. I can have all the styles exactly the way I need, but I can only have one colour version of the tooltip whereas I need two.

    Please can you let me know if a small tweak in the code could make this possible? Thanks!

    question 
    Reply
  • Is there a way to use components as child elements?
    Is there a way to use components as child elements?

    Mar 16, 2018

    <span>

    <ReactHint delay={100} attribute="label" className={cp-tooltip-${mode}} events={{hover: true}} />

    <Icon.Information label="Left" label-at="left" /> <button label="Top" label-at="top">Top</button>

    </span>

    I would like to be able use a component (Icon.Information) in place of a single element (button).

    The button renders and has the correct tooltip. The component renders but has no tooltip.

    Could you please advise be how this might be possible? Thanks

    question 
    Reply
  • Allow setting different show & hide delay
    Allow setting different show & hide delay

    Aug 19, 2018

    Is this possible? Looking at the code I only see a single timer for this, is there any hook I can use to customize this? I want to have long show delay but a short hide to avoid spamming the UI with tooltip popups as user moves the mouse around. Tooltips should only show up if user is confused what given control does and pauses for a second, but once tooltip is visible and user moves the cursor away I don't want to keep that old popup visible for another second, it should hide instantly when not needed. Thanks!

    enhancement 
    Reply
  • does this have typescript definitions?
    does this have typescript definitions?

    Nov 9, 2018

    i'd like to use it for my project but my project uses typescript. can it work in typescript?

    enhancement question 
    Reply
  • Multiple instances of react-hint: how to use ReactHintFactory properly?
    Multiple instances of react-hint: how to use ReactHintFactory properly?

    Sep 6, 2017

    I'm having great trouble using react-hint across multiple, individual components in my app.

    If react-hint is included multiple times, I end up with multiple tooltips appears on every hover. I think it's because I'm setting up each instance using:

    const ReactHint = ReactHintFactory(React)

    and so ReactHint is using the same root but I don't understand how I can set up a new instance specific to the component in which it's embedded, if that makes sense?!

    https://gist.github.com/howells/f7de166446d75537a0a50ab9de205ba4

    this component, UserActions, can appear many many times within an component, so I need the hints to be scoped to just that instance of the component.

    Any help much appreciated!

    question 
    Reply
  • Hint positioning both vertically and horizontally
    Hint positioning both vertically and horizontally

    Jun 14, 2018

    It would be great if it was possible to position the hint both vertically and horizontally, like position="topLeft" or horizontalPosition="left" verticalPosition="top" or similar.

    https://github.com/slmgc/react-hint/blob/1cb28df13617bb7b07650c44356738d68a5fdccf/src/index.js#L146

    We extended the switch statement in getHintData to handle our particular use case, but this approach may not be practical as a general solution.

          case 'topLeft':
            top = 0;
            left = -hintWidth;
            break;
    

    What would be your suggestion?

    enhancement wontfix 
    Reply
  • Changelog
    Changelog

    Aug 24, 2017

    Thank you for your library!

    What are breaking (and non-breaking) changes in v2? I can't find changelog anywhere. Would you mind add it, please?

    enhancement question 
    Reply
  • Global Settings in ReactHint Props
    Global Settings in ReactHint Props

    May 10, 2017

    It would be great if we could provide global settings/defaults to ReactHint through the component, for example:

    <ReactHint displayDelay=100 hideDelay=300 transition="slide" />
    

    Also, supporting those settings would be great as well ;)

    enhancement 
    Reply
  • pointer-events: none, for hints
    pointer-events: none, for hints

    Aug 28, 2017

                                                                                                                                                                                                           
    Reply
  • Migration to v3 from v1
    Migration to v3 from v1

    Nov 13, 2017

    Currently we use [email protected] We have <ReactHint /> in top container component to avoid conflicts with multiple instances in v1. Half of our components use data-rh-cls. Is there a strategy how to migrate to v3 when only half of components uses data-rh-cls?

    Is this possible to get back data-rh-cls back, which was deprecated in 2.0.0?

    Thank you for your library!

    question 
    Reply