Stateless tooltip component using React hooks and ref
$begingroup$
What I'm trying to create is a generic tooltip component that I can use anywhere. I decided on a stateless component and the use of hooks and ref.
Explanation 💡
Hook, because I wanted to have a flag for hover and ref to get element width and/or height to center it correctly.
Problem ❓
Is this solution correct and will perform fast enough with the bigger amount of tooltip components on the same page (over 100)?
The solutions I've come across so far have not been satisfactory. I wonder if this idea makes a sense if so, I will develop it and publish it as an open source project (I would like to finally make a contribution! 😄)
Demo: https://danzawadzki.github.io/react-tooltip/
Repository: https://github.com/danzawadzki/react-tooltip
Tooltip.tsx
import * as React from 'react';
import './Tooltip.scss';
import {CSSTransition} from 'react-transition-group';
import {useState} from 'react';
import {useRef} from 'react';
export interface ITooltip {
/** Tooltip position. */
position?: 'top' | 'left' | 'right' | 'bottom';
/** Tooltip message content. */
message: String;
/** Array with details about columns to render. */
children: JSX.Element | Array<JSX.Element> | string;
}
export interface ITooltipStyle {
/** Distance to the left edge. */
left?: string;
/** Distance to the top edge. */
top?: string;
/** Distance to the bottom edge. */
bottom?: string;
/** Distance to the right edge. */
right?: string;
}
/**
* Tooltip component.
*
* @author Daniel Zawadzki <hello@danielzawadzki.com>
* @version 1.0.0
*/
const Tooltip: React.FunctionComponent<ITooltip> = ({position = 'top', message, children}) => {
/**
* Hook toggling tooltip isVisible flag.
*/
const [isVisible, setIsVisible] = useState<boolean>(false);
const toggle = () => setIsVisible(!isVisible);
/**
* Reference to the component to track width and height
*/
const node = useRef<any>(null);
/**
* Positioning the component using the reference
*/
let style: ITooltipStyle = {};
if (node.current && node.current.offsetWidth) {
if (position === 'top' || position === 'bottom') {
style.left = `${String(node.current.offsetWidth / 2)}px`;
} else {
style.top = `-${String(node.current.offsetHeight / 1.5)}px`;
}
}
return (
<div className="Tooltip">
<div
className="Tooltip__toggler"
onMouseOverCapture={toggle}
onMouseOut={toggle}
ref={node}>
{children}
</div>
<CSSTransition
in={isVisible}
timeout={200}
classNames="Tooltip"
unmountOnExit>
<div
className={`Tooltip__message Tooltip__message--${position}`}
style={style}>
{message}
</div>
</CSSTransition>
</div>
);
};
export default Tooltip;
I know that there are probably a lot of bugs and areas for development there, but is it any good idea?
react.js typescript jsx
$endgroup$
add a comment |
$begingroup$
What I'm trying to create is a generic tooltip component that I can use anywhere. I decided on a stateless component and the use of hooks and ref.
Explanation 💡
Hook, because I wanted to have a flag for hover and ref to get element width and/or height to center it correctly.
Problem ❓
Is this solution correct and will perform fast enough with the bigger amount of tooltip components on the same page (over 100)?
The solutions I've come across so far have not been satisfactory. I wonder if this idea makes a sense if so, I will develop it and publish it as an open source project (I would like to finally make a contribution! 😄)
Demo: https://danzawadzki.github.io/react-tooltip/
Repository: https://github.com/danzawadzki/react-tooltip
Tooltip.tsx
import * as React from 'react';
import './Tooltip.scss';
import {CSSTransition} from 'react-transition-group';
import {useState} from 'react';
import {useRef} from 'react';
export interface ITooltip {
/** Tooltip position. */
position?: 'top' | 'left' | 'right' | 'bottom';
/** Tooltip message content. */
message: String;
/** Array with details about columns to render. */
children: JSX.Element | Array<JSX.Element> | string;
}
export interface ITooltipStyle {
/** Distance to the left edge. */
left?: string;
/** Distance to the top edge. */
top?: string;
/** Distance to the bottom edge. */
bottom?: string;
/** Distance to the right edge. */
right?: string;
}
/**
* Tooltip component.
*
* @author Daniel Zawadzki <hello@danielzawadzki.com>
* @version 1.0.0
*/
const Tooltip: React.FunctionComponent<ITooltip> = ({position = 'top', message, children}) => {
/**
* Hook toggling tooltip isVisible flag.
*/
const [isVisible, setIsVisible] = useState<boolean>(false);
const toggle = () => setIsVisible(!isVisible);
/**
* Reference to the component to track width and height
*/
const node = useRef<any>(null);
/**
* Positioning the component using the reference
*/
let style: ITooltipStyle = {};
if (node.current && node.current.offsetWidth) {
if (position === 'top' || position === 'bottom') {
style.left = `${String(node.current.offsetWidth / 2)}px`;
} else {
style.top = `-${String(node.current.offsetHeight / 1.5)}px`;
}
}
return (
<div className="Tooltip">
<div
className="Tooltip__toggler"
onMouseOverCapture={toggle}
onMouseOut={toggle}
ref={node}>
{children}
</div>
<CSSTransition
in={isVisible}
timeout={200}
classNames="Tooltip"
unmountOnExit>
<div
className={`Tooltip__message Tooltip__message--${position}`}
style={style}>
{message}
</div>
</CSSTransition>
</div>
);
};
export default Tooltip;
I know that there are probably a lot of bugs and areas for development there, but is it any good idea?
react.js typescript jsx
$endgroup$
add a comment |
$begingroup$
What I'm trying to create is a generic tooltip component that I can use anywhere. I decided on a stateless component and the use of hooks and ref.
Explanation 💡
Hook, because I wanted to have a flag for hover and ref to get element width and/or height to center it correctly.
Problem ❓
Is this solution correct and will perform fast enough with the bigger amount of tooltip components on the same page (over 100)?
The solutions I've come across so far have not been satisfactory. I wonder if this idea makes a sense if so, I will develop it and publish it as an open source project (I would like to finally make a contribution! 😄)
Demo: https://danzawadzki.github.io/react-tooltip/
Repository: https://github.com/danzawadzki/react-tooltip
Tooltip.tsx
import * as React from 'react';
import './Tooltip.scss';
import {CSSTransition} from 'react-transition-group';
import {useState} from 'react';
import {useRef} from 'react';
export interface ITooltip {
/** Tooltip position. */
position?: 'top' | 'left' | 'right' | 'bottom';
/** Tooltip message content. */
message: String;
/** Array with details about columns to render. */
children: JSX.Element | Array<JSX.Element> | string;
}
export interface ITooltipStyle {
/** Distance to the left edge. */
left?: string;
/** Distance to the top edge. */
top?: string;
/** Distance to the bottom edge. */
bottom?: string;
/** Distance to the right edge. */
right?: string;
}
/**
* Tooltip component.
*
* @author Daniel Zawadzki <hello@danielzawadzki.com>
* @version 1.0.0
*/
const Tooltip: React.FunctionComponent<ITooltip> = ({position = 'top', message, children}) => {
/**
* Hook toggling tooltip isVisible flag.
*/
const [isVisible, setIsVisible] = useState<boolean>(false);
const toggle = () => setIsVisible(!isVisible);
/**
* Reference to the component to track width and height
*/
const node = useRef<any>(null);
/**
* Positioning the component using the reference
*/
let style: ITooltipStyle = {};
if (node.current && node.current.offsetWidth) {
if (position === 'top' || position === 'bottom') {
style.left = `${String(node.current.offsetWidth / 2)}px`;
} else {
style.top = `-${String(node.current.offsetHeight / 1.5)}px`;
}
}
return (
<div className="Tooltip">
<div
className="Tooltip__toggler"
onMouseOverCapture={toggle}
onMouseOut={toggle}
ref={node}>
{children}
</div>
<CSSTransition
in={isVisible}
timeout={200}
classNames="Tooltip"
unmountOnExit>
<div
className={`Tooltip__message Tooltip__message--${position}`}
style={style}>
{message}
</div>
</CSSTransition>
</div>
);
};
export default Tooltip;
I know that there are probably a lot of bugs and areas for development there, but is it any good idea?
react.js typescript jsx
$endgroup$
What I'm trying to create is a generic tooltip component that I can use anywhere. I decided on a stateless component and the use of hooks and ref.
Explanation 💡
Hook, because I wanted to have a flag for hover and ref to get element width and/or height to center it correctly.
Problem ❓
Is this solution correct and will perform fast enough with the bigger amount of tooltip components on the same page (over 100)?
The solutions I've come across so far have not been satisfactory. I wonder if this idea makes a sense if so, I will develop it and publish it as an open source project (I would like to finally make a contribution! 😄)
Demo: https://danzawadzki.github.io/react-tooltip/
Repository: https://github.com/danzawadzki/react-tooltip
Tooltip.tsx
import * as React from 'react';
import './Tooltip.scss';
import {CSSTransition} from 'react-transition-group';
import {useState} from 'react';
import {useRef} from 'react';
export interface ITooltip {
/** Tooltip position. */
position?: 'top' | 'left' | 'right' | 'bottom';
/** Tooltip message content. */
message: String;
/** Array with details about columns to render. */
children: JSX.Element | Array<JSX.Element> | string;
}
export interface ITooltipStyle {
/** Distance to the left edge. */
left?: string;
/** Distance to the top edge. */
top?: string;
/** Distance to the bottom edge. */
bottom?: string;
/** Distance to the right edge. */
right?: string;
}
/**
* Tooltip component.
*
* @author Daniel Zawadzki <hello@danielzawadzki.com>
* @version 1.0.0
*/
const Tooltip: React.FunctionComponent<ITooltip> = ({position = 'top', message, children}) => {
/**
* Hook toggling tooltip isVisible flag.
*/
const [isVisible, setIsVisible] = useState<boolean>(false);
const toggle = () => setIsVisible(!isVisible);
/**
* Reference to the component to track width and height
*/
const node = useRef<any>(null);
/**
* Positioning the component using the reference
*/
let style: ITooltipStyle = {};
if (node.current && node.current.offsetWidth) {
if (position === 'top' || position === 'bottom') {
style.left = `${String(node.current.offsetWidth / 2)}px`;
} else {
style.top = `-${String(node.current.offsetHeight / 1.5)}px`;
}
}
return (
<div className="Tooltip">
<div
className="Tooltip__toggler"
onMouseOverCapture={toggle}
onMouseOut={toggle}
ref={node}>
{children}
</div>
<CSSTransition
in={isVisible}
timeout={200}
classNames="Tooltip"
unmountOnExit>
<div
className={`Tooltip__message Tooltip__message--${position}`}
style={style}>
{message}
</div>
</CSSTransition>
</div>
);
};
export default Tooltip;
I know that there are probably a lot of bugs and areas for development there, but is it any good idea?
react.js typescript jsx
react.js typescript jsx
edited 18 mins ago
200_success
130k16153419
130k16153419
asked 1 hour ago
Dan ZawadzkiDan Zawadzki
162
162
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215217%2fstateless-tooltip-component-using-react-hooks-and-ref%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215217%2fstateless-tooltip-component-using-react-hooks-and-ref%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown