Visibility sensor is not working with counterup - reactjs

I have been trying to implement react counterup along with react-visibility sensor. I wish to show the couterup only after that section is visible in the viewport. So, Using the visibility sensor to load it. But, it's not working and below is the error
"Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object."
Sample code is below
import React from 'react';
import CountUp, { startAnimation } from 'react-countup';
const CounterSection= (props) => {
const VisibilitySensor = require('react-visibility-sensor');
function onChange(isVisible) {
console.log('Element is now %s', isVisible ? 'visible' : 'hidden');
}
return (
<div>
<VisibilitySensor onChange={onChange}>
<div>...content goes here...</div>
<CountUp start={0} end={9350} duration={5} />+
</VisibilitySensor>
</div>
);
};
Assistance on this much appreciated. Thanks

The error was that the contents inside any component should be within a single block. Also, visibility sensor should be imported and removed from required. I added the contents inside div and the error was off.
import CountUp, { startAnimation } from 'react-countup';
import VisibilitySensor from 'react-visibility-sensor';
const CounterSection= (props) => {
function onChange(isVisible) {
console.log('Element is now %s', isVisible ? 'visible' : 'hidden');
}
return (
<div>
<VisibilitySensor onChange={onChange}>
<div>
<div>...content goes here...</div>
<CountUp start={0} end={9350} duration={5} />+
</div>
</VisibilitySensor>
</div>
);
};

As a previous poster pointed out, you're importing the library improperly, import it with an import statement at the top of the component. Using the ES5 require is correct for Node/Express but not for React.
You don't need to manually handle the onChange, that's apparently an older way of getting the two libraries to work with each other. Check out this answer pertaining to using VisibilitySensor and CountUp. I just tested it out and it works for me.

VisibilitySensor does not support the feature to track the first time visibility out of the box. I will put here an example that I have been using.
AppearVisibility.js
import React, { useState } from "react";
import VisibilitySensor from "react-visibility-sensor";
/**
* VisibilitySensor does not implement some kind of funcionality to track first time
* visibility. This component extends VisibilitySensor compoment to provide this
* feature. Just use `hasBeenVisible` render prop instead of `isVisible`.
*
* https://github.com/joshwnj/react-visibility-sensor/issues/117#issuecomment-686365798
*/
const AppearSensor = ({
onChange,
children,
...rest
}) => {
const [hasBeenVisible, setHasBeenVisible] = useState(false);
return (
<VisibilitySensor {...rest} onChange={(isVisible) => {
if (isVisible) setHasBeenVisible(true)
if (onChange) onChange(isVisible)
}}>
{
({
isVisible,
...restRenderProps
}) => {
return children({ isVisible, ...restRenderProps, hasBeenVisible })
}
}
</VisibilitySensor>
);
};
AppearSensor.propTypes = VisibilitySensor.propTypes
AppearSensor.defaultProps = VisibilitySensor.defaultProps
export default AppearSensor;
CounterSection.js
import React from 'react';
import CountUp from 'react-countup';
const CounterSection = (props) => {
return (
<div>
<div>...content goes here...</div>
<AppearSensor>
{({ hasBeenVisible }) =>
hasBeenVisible
? <CountUp
start={0}
end={9350}
duration={5} />
: <span>9350</span>
}
</AppearSensor>
</div>
);
};
export default CounterSection;

import React, {Fragment,Component} from 'react';
import CountUp, { startAnimation } from 'react-countup';
import ReactVisibilitySensor from "react-visibility-sensor";
class Test extends Component {
render() {
return (
<Fragment>
<h1 className={'Countnumber'}>
<CountUp start={0} end={100} delay={0}>
{({ countUpRef,start }) => (
<ReactVisibilitySensor onChange={start} delayedCall={true}>
<span ref={countUpRef} />
</ReactVisibilitySensor>
)}
</CountUp>
</h1>
</Fragment>
);
}
}
export default Test;
This definitely works

Related

React Transition Group: How to pass ref to mapped component to avoid Warning: findDOMNode is deprecated in StrictMode (still not working)

I am using "React Transition Group" ver 4.4.2 to animate menu buttons (CSSTransitions mapped inside TransitionGroup), but when I click them ones or a couple of times I get the warning:
"Warning: findDOMNode is deprecated in StrictMode. findDOMNode was
passed an instance of Transition which is inside StrictMode. Instead,
add a ref directly to the element you want to reference. Learn more
about using refs safely here:
https://reactjs.org/link/strict-mode-find-node"
I know, I know, I have to use refs to get rid of that warning, so I did as it was described in these tickets:
Stackoverflow ticket 66587359
Stackoverflow ticket 63677852
But I still have the warning! I just have to click the button onece or click different menu buttons a couple of times. What is intresting is that I get that warning only once, than I have to reload page to get it again.
I have inserted my part of code to the codesandbox:
CodeSandbox
Basically the code we need to look at is in NavigationItems.js and NavigationItem.js (src/components-satateLess/Navigation/NavigationItems... etc.)
As you can see I use useRef hook inside NavigationItem.js component that is mapped in NavigationItems.js:
import React, { useState } from "react";
import { TransitionGroup } from "react-transition-group";
import classes from "./NavigationItems.module.scss";
import routerButtons from "../../../router/mainMenu";
import NavigationItem from "./NavigationItem/NavigationItem"
const NavigationItems = () => {
const [allButtons, setAllButtons] = useState(routerButtons);
const [prevButton, setPrevButton] = useState({
in:false, id:-1, desc:"",href:"",element:null
});
const allButtonsDeepUpdate = (idx, obj) => {
const allButtonsCpy = [];
for(let i=0;i<allButtons.length;i++) {
if(i===idx) {
allButtonsCpy.push(Object.assign({},obj));
} else if (i===prevButton.id) {
allButtonsCpy.push(Object.assign({},prevButton));
} else {
allButtonsCpy.push(Object.assign({},allButtons[i]));
};
};
setAllButtons(allButtonsCpy);
};
const enterAnimation = (idx) => {
//this contition checks if button wasn't already animated.
if(allButtons[idx].id !== prevButton.id) {
const newButton = {...allButtons[idx], ...{in:true}};
if (prevButton.id !== -1)
setPrevButton({...prevButton,...{in:false}});
allButtonsDeepUpdate(idx, newButton);
setPrevButton(Object.assign({},allButtons[idx]));
}
};
return (
<div>
<TransitionGroup component="ul" className={classes.NavigationItems}>
{allButtons.map((button) => (
<NavigationItem
key={button.id}
starter={button.in}
timeout={1000}
click={enterAnimation.bind(this,button.id)}
link={button.href}
>
{button.desc}
</NavigationItem>
))}
</TransitionGroup>
</div>
);
};
export default NavigationItems;
Then NavigationItem.js:
import React, { useEffect, useRef } from 'react';
import { NavLink } from 'react-router-dom';
import { CSSTransition } from 'react-transition-group';
import classes from './NavigationItem.module.scss';
const NavigationItem = props => {
const ref = useRef(null);
useEffect(() => {
console.log("Ref has changed: ",ref.current);
},[ref.current])
return (
<CSSTransition
nodeRef={ref}
in={props.starter}
classNames={{
enter: classes.NavigationItemEnter,
enterActive: classes.NavigationItemEnterActive,
enterDone: classes.NavigationItemEnterDone,
exit: classes.NavigationItemExit,
exitActive: classes.NavigationItemExitActive,
exitDone: classes.NavigationItemExitDone,
}}
timeout={props.timeout}
>
<li ref={ref} className={classes.NavigationItem} onClick={props.click}>
<NavLink
//activeClassName={classes.active}
//className={({isActive}) => isActive ? classes.active : ''}
to={props.link}
exact={props.exact}
>
{props.children}
</NavLink>
</li>
</CSSTransition>
);
}
export default NavigationItem;
I've also used React.createRef() and forwardRef() with similar results.
Am I missing something, or is it caused by "React Transition Group" liblary?
If so, were can I report that?
As always your answers and suggestions are more than welcome!
Thanks in advance!!!
I had a similar problem, and I finally solved it by wrapping the inner component in the map in a div and passing the ref to the div instead of the component. I couldn't make it work by giving the ref via React.forwardRef.
Also, the naming of the elements inside TransitionGroup is suspicious. Seems like enterDone means enter-active but it's not similar with exit. Take note of what my CSS is and what the effect is.
// Inside List.js
const getItemsToDo = () => {
let checkedCopy = [...checked];
return (
<TransitionGroup className="todo-items">
{checkedCopy.reverse().map((check, index) => {
if (!check) {
let reverse_index = checked.length - 1 - index;
const textHandler = onTextChangeHandler(reverse_index);
const checkboxHandler = onCheckboxChangeHandler(reverse_index);
const itemRef = createRef();
return (
<CSSTransition
key={reverse_index}
timeout={500}
classNames={{
enterDone: styles["todo-item-enter-active"],
enterActive: styles["todo-item-enter"],
exitActive: styles["todo-item-exit-active"],
exitDone: styles["todo-item-exit"],
}}
nodeRef={itemRef}
>
<div ref={itemRef}>
<ListElement
onTextChange={textHandler}
onCheckboxChange={checkboxHandler}
checked={check}
text={texts[reverse_index]}
key={reverse_index}
/>
</div>
</CSSTransition>
);
} else return null;
})}
</TransitionGroup>
);
};
// ListElement.js
import React from "react";
const ListElement = (props) => {
const { checked, text, onCheckboxChange, onTextChange, readOnly } = props;
return (
<form onSubmit={(e) => e.preventDefault()}>
<input type="checkbox" checked={checked} onChange={onCheckboxChange} />
<input
type="text"
value={text}
onChange={onTextChange}
readOnly={readOnly}
/>
</form>
);
};
export default ListElement;
/* List.module.css */
.todo-item-enter {
opacity: 0;
}
.todo-item-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.todo-item-exit {
opacity: 1;
}
.todo-item-exit-active {
opacity: 0;
transform: translateY(2rem);
transition: all 500ms ease-in-out;
}

How to prevent component from being re-rendered unnecessarily

I'll start with the code. I have a stateless functional component that resembles this
export const Edit Topic = (_title, _text) {
const [title, setTitle] = useState(_title)
const [text, setText] = useState(_text)
return (
<>
<InputText props={{ fieldName:"Title:", value:title, setValue:setTitle, placeHolder:"Topic Title"}}/>
<InputTextArea props={{ fieldName:"Markdown Text:", text, setText }}/>
<PreviewBox text={text}/>
</>
)
}
I have PreviewBox when it's on, page rendering takes a bit longer because text can be quite long. PreviewBox needs to re-render each time I change text in InputTextArea and that's fine.
The problem I'm having is when I change the value of title it's also updating <PreviewBox/> which is undesired.
How can I make sure that <PreviewBox/> only updates when text changes and not when title changes?
The reason why I believe the re-rendering is occuring is because if I toggle off PreviewBox, there's no lag in when updating title but when PreviewBox is visible the updating the title lags.
import style from "../styles/CreateTopic.module.css"
import { Component } from "react"
import Markdown from "./Markdown";
export class PreviewBox extends Component {
constructor(props) {
super(props)
this.state = {
isShow: true
}
}
toggleShow = () => {
console.log("begin isShow", this.state)
this.setState(state => ({ isShow: !state.isShow}))
}
render() {
return (
<>
<div className={style.wrptoggle}>
<button className={style.btn} onClick={this.toggleShow}>Preview</button>
</div>
{this.state.isShow ?
<div className={style.wrppreviewbox}>
<div className={style.previewbox}>
<Markdown text={this.props.text}/>
</div>
</div>
: null}
</>
)
}
}
Since the above also contains <Markdown/> here's that component:
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
const Markdown = ({text}) => {
return (
<div>
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
children={text}
/>
</div>
);
}
export default Markdown;
I don't see any complexity in PreviewBox that would cause any rendering delay so I might assume it's the Markdown component that may take some time "working" when it's rerendered since you say "toggle off PreviewBox, there's no lag in when updating title".
Solution
You can use the memo Higher Order Component to decorate the Markdown component and provide a custom areEqual props compare function.
import { memo } from 'react';
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
const Markdown = ({ text }) => {
return (
<div>
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
children={text}
/>
</div>
);
};
export default memo(Markdown);
By default it will only shallowly compare complex objects in the props
object. If you want control over the comparison, you can also provide
a custom comparison function as the second argument.
const areEqual = (prevProps, nextProps) => {
return prevProps.text === nextProps.text;
};
export default memo(Markdown, areEqual);

Get ref from connected redux component withStyles

I have this export of a working component:
export default connect(
mapStateToProps, actions,
null, { withRef: true, forwardRef: true }
)(withTheme()(withStyles(styles)(MainMenu)));
And its call:
<MainMenu
ref={(connectedMenu) => this.menuRef = connectedMenu.getWrappedInstance()}
user={user}
/>
I've expected to get a MainMenu ref, but I keep getting WithTheme object instead.
I've also tried to get through innerRef, but got the following errors:
TypeError: connectedMenu.getWrappedInstance is not a function
TypeError: Cannot read property 'getWrappedInstance' of null
Before all of that I've tried that React.createRef() format, but it didn't worked.
How do I get this ref?
Assuming you are using v4 of Material-UI, your syntax for withTheme is incorrect. In v4 the first set of parentheses was removed.
Instead of
withTheme()(YourComponent)
you should have
withTheme(YourComponent)
Below is code from a modified version of the react-redux todo list tutorial that shows the correct syntax. I've included here the two files that I changed (TodoList.js and TodoApp.js), but the sandbox is a fully working example.
In TodoApp, I use the ref on TodoList to get and display its height. The displayed height will only get updated if TodoApp re-renders, so I've included a button to trigger a re-render. If you add a couple todos to the todo list, and then click the re-render button, you will see that the new height of the list is displayed (showing that the ref is fully working).
In TodoList, I'm using withStyles to add a blue border around the todo list to show that withStyles is working, and I'm displaying the primary color from the theme to show that withTheme is working.
TodoList.js
import React from "react";
import { connect } from "react-redux";
import Todo from "./Todo";
import { getTodosByVisibilityFilter } from "../redux/selectors";
import { withStyles, withTheme } from "#material-ui/core/styles";
import clsx from "clsx";
const styles = {
list: {
border: "1px solid blue"
}
};
const TodoList = React.forwardRef(({ todos, theme, classes }, ref) => (
<>
<div>theme.palette.primary.main: {theme.palette.primary.main}</div>
<ul ref={ref} className={clsx("todo-list", classes.list)}>
{todos && todos.length
? todos.map((todo, index) => {
return <Todo key={`todo-${todo.id}`} todo={todo} />;
})
: "No todos, yay!"}
</ul>
</>
));
const mapStateToProps = state => {
const { visibilityFilter } = state;
const todos = getTodosByVisibilityFilter(state, visibilityFilter);
return { todos };
};
export default connect(
mapStateToProps,
null,
null,
{ forwardRef: true }
)(withTheme(withStyles(styles)(TodoList)));
TodoApp.js
import React from "react";
import AddTodo from "./components/AddTodo";
import TodoList from "./components/TodoList";
import VisibilityFilters from "./components/VisibilityFilters";
import "./styles.css";
export default function TodoApp() {
const [renderIndex, incrementRenderIndex] = React.useReducer(
prevRenderIndex => prevRenderIndex + 1,
0
);
const todoListRef = React.useRef();
const heightDisplayRef = React.useRef();
React.useEffect(() => {
if (todoListRef.current && heightDisplayRef.current) {
heightDisplayRef.current.innerHTML = ` (height: ${
todoListRef.current.offsetHeight
})`;
}
});
return (
<div className="todo-app">
<h1>
Todo List
<span ref={heightDisplayRef} />
</h1>
<AddTodo />
<TodoList ref={todoListRef} />
<VisibilityFilters />
<button onClick={incrementRenderIndex}>
Trigger re-render of TodoApp
</button>
<div>Render Index: {renderIndex}</div>
</div>
);
}

Is there a way in React Javascript to pass props and use it in external import?

I want to pass props from one component to another, and use it in the second one for an import above the component declaration
This is for using the same component, with no need to create it 4 times, every time with another SVG.
I'm using React, Javascript, Webpack, babel.
I'm also using svgr/webpack to create a component from an SVG picture, and it's crucial for me to use SVG not < img >.
import React from 'react';
import RightNavItem from './right_nav_item';
const RightNav = ({navitems}) => {
const rightNavItems = navitems.map( (item) => {
return <RightNavItem name={ item }/>
});
return(
<div className="rightnav">
{rightNavItems}
</div>
);
};
.
export default RightNav;
import React from 'react';
const RightNavItem = ({ name }) => {
const svgpath = `../../../../resources/img/navbar/${name}.svg`;
return(
<div>
<img src={ svgpath } style={{height: '25px'}}/>
<span>{ name }</span>
</div>
);
};
export default RightNavItem;
And I want to achieve being able to do this:
import React from 'react';
import SvgPicture from '../../../../resources/img/navbar/{name}.svg';
const RightNavItem = ({ name }) => {
return(
<div>
<SvgPicture />
<span>{ name }</span>
</div>
);
};
export default RightNavItem;
.
Ok so I went back and implemented the whole thing on my local app to get exactly what you need. I am editing my original answer. Hope this solves your issue.
The parent:
import React from 'react';
import { ReactComponent as svg } from 'assets/img/free_sample.svg';
import RightNavItem from './RightNavItem';
const LOGOS = [
{ name: 'home', svg },
{ name: 'home', svg },
];
const RightNav = () => (
<div>
{LOGOS.map(logo => (
<RightNavItem name={logo.name}>
<logo.svg />
</RightNavItem>
))}
</div>
);
export default RightNav;
The child:
import React from 'react';
const RightNavItem = ({ name, children }) => (
<div>
{children}
<span>{name}</span>
</div>
);
export default RightNavItem;
You don't need to import the svg as I did, if you are able to use svg as a component in your webpack config then continue to do what you were doing before.
I managed to do it in a kind of ugly way, but it works.
The problem is if I have more than 4 items, then using it without the map() function can be really annoying.
I used {props.children}, and instead of using map(), I added the 4 times, each with different 'SVG' component child and different props 'name', that way the component only gets initialized at the RightNavItem level.
IF SOMEONE KNOWS how can I use this with the map() function, It'll help a lot!
Thanks to everyone who helped!
For example:
const RightNav = (props) => {
return(
<div className = "rightnav">
<RightNavItem name = {home}>
<HomeSVG />
</RightNavItem>
<RightNavItem name = {profile}>
<ProfileSVG />
</RightNavItem>
.
.
.
</div>
);
};
And in the RightNavItem:
const RightNavItem = (props) => {
return(
<div>
{props.children}
<span>{ props.name }</span>
</div>
);
};

reactstrap tooltip dynamic id

I am developing a react application and using reactstrap.
I am using Tooltip Component of reactstrap which requires a target attribute, a value of target element's id. This id is being geneated dynamically and seems reactstrap tooltip doesn't like it.
Component looks like:
MovieCard.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Col, Card, CardImg, CardBody, CardTitle, CardSubtitle, CardText, Button, Tooltip } from 'reactstrap';
import { LimitedTextTitle } from '../custom-styled/CustomStyledComponents';
class MovieCard extends Component {
constructor (props) {
super(props);
this.state = {
open: false
};
this.toggle = this.toggle.bind(this);
}
toggle () {
this.setState({
open: !this.state.open
})
}
render () {
const { imdbID, Title, Year, Rated, Plot, Country, Poster } = this.props.movie;
return (
<Col md="4">
<Card>
<CardImg
top
width="100%"
src={Poster}
alt="blah"
/>
</Card>
<CardBody>
<CardTitle>
<LimitedTextTitle id={imdbID}>
{`${Title} - (${Year})`}
</LimitedTextTitle>
<Tooltip placement='top' target={imdbID} isOpen={this.state.open} toggle={this.toggle}>
{Title}
</Tooltip>
</CardTitle>
<CardSubtitle>{`Rated: ${Rated} Country: ${Country}`}</CardSubtitle>
<CardText>{Plot}</CardText>
<Button>Read More</Button>
</CardBody>
</Col>
);
}
}
MovieCard.propTypes = {
movie: PropTypes.object.isRequired // eslint-disable-line
};
export default MovieCard;
Any suggestions?
react vesion 16.2.0
reactstrap 5.0.0-alpha.4
Was dealing with a similar problem.
Adding the code as an answer because i cannot add a comment above...
Hope it will help you or anyone else who will come across this question.
Description:
Use reactstrap tooltip for elements that are getting generated dynamically.
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Button, Tooltip } from 'reactstrap';
class App extends React.Component {
state = {};
toggle = targetName => {
if (!this.state[targetName]) {
this.setState({
...this.state,
[targetName]: {
tooltipOpen: true
}
});
} else {
this.setState({
...this.state,
[targetName]: {
tooltipOpen: !this.state[targetName].tooltipOpen
}
});
}
};
isToolTipOpen = targetName => {
return this.state[targetName] ? this.state[targetName].tooltipOpen : false;
};
render() {
return (
<div>
{[1, 2, 3, 4, 5, 6].map((x, i) => (
<div key={`div-${i}`}>
<Button color="link" id={`btn-${i}`}>
{x}
</Button>
<Tooltip
placement="right"
isOpen={this.isToolTipOpen(`btn-${i}`)}
target={`btn-${i}`}
toggle={() => this.toggle(`btn-${i}`)}>
Hello world!
</Tooltip>
</div>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
react: 16.9.0
reactstrap: 8.0.1
https://codesandbox.io/embed/angry-taussig-fup7i?fontsize=14
EUREKA I GOT IT!!! Building on Meir Keller's answer, there's no need to check if that state for the tooltip already exist. If it doesn't exist, it's false by default...
So long as state is defined, even if it's an empty state, this works.
This is using reactstrap's Popover, but it's the same concept.
import React, { Component, Fragment } from 'react';
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css'
import { Container, Row, Col, Input, Button, Popover } from 'reactstrap';
class App extends Component {
state = {};
toggle = (target) => {
// console.log(typeof target) // make sure this is a string
this.setState({
...state,
[target]: !this.state[target]
});
};
render() {
return (
<Container>
{["Hello", "Greetings"].map((name) => (
<Row>
<Fragment>
<Button id={name} type="button">{name}</Button>
<Popover placement="right"
isOpen={this.state[`${name}`]}
target={name}
toggle={() => this.toggle(`${name}`)}>
<PopoverBody>
You've got mail. Did you know?
</PopoverBody>
</Popover>
</Fragment>
</Row>
))}
</Container>
);
}
}
export default App;
Create a new component in modular or component directory and paste this code
import React, { useState } from "react";
import { Tooltip } from "reactstrap";
const TooltipItem = props => {
const { position='top', id } = props;
const [tooltipOpen, setTooltipOpen] = useState(false);
const toggle = () => setTooltipOpen(!tooltipOpen);
return (
<span>
<span id={"tooltip-" + id}>
{props.children}
</span>
<Tooltip
placement={position}
isOpen={tooltipOpen}
target={"tooltip-" + id}
toggle={toggle}
>
{props.title}
</Tooltip>
</span>
);
};
export default TooltipItem;
Now import and use this tooltip component
import TooltipItem from "../Tooltip";
<TooltipItem id={'edit' + data.id} title={'Edit Store'}>
<i className="fas fa-edit pointer" onClick={() => this.onEditClick(data)}/>
</TooltipItem>
I will Like to add an answer for it as already many people have mentioned many ways to deal with the problem.
But reactStrap works perfectly fine, mistakes most of the beginners are doing that while creating id they are using special characters like:
- _ / # and it can even be a space
Just keep the id a very simple combination of chars and numbers reactstrap will work totally fine
New component UncontrolledTooltip will solve the problem. Just use
<UncontrolledTooltip
placement="right"
target={`btn-${i}`}
>
{props.title}
</UncontrolledTooltip>
I tried a lot of solutions and was still having trouble with Reactstrap Tooltip crashing when the target element is not in the Dom.
I combined a couple other solutions that people posted and this is the only way it worked for me. Conditional rendering FTW.
const ElementWithTooltip = ({
dynamicIdentifier, // string, number, w/e
}): ReactElement => {
// Target element state.
const [isTargetReady, setIsTargetReady] = useState(false);
// Target element ref.
const tooltipRef = useRef(null);
// Hook to recognize that the target is ready.
useEffect(() => {
const targetElement = tooltipRef.current;
if (targetElement) {
setIsTargetReady(true);
}
}, [tooltipRef.current]);
// TSX.
return (
<>
<span ref={tooltipRef}>This is the target element</span>
{isTargetReady && <UncontrolledTooltip autohide={false} target={tooltipRef}>
Tooltippy text stuff
</UncontrolledTooltip>}
</>
);
The imdbID most probably is starting with digit i.e. 123abcdefghijklmno1234567890
Remember that tooltips can't work in that case when ID starts with a number i.e. the Tooltip's target cannot start with an integer.
all you need to do here is, change this:
<CardTitle>
<LimitedTextTitle id={imdbID}>
{`${Title} - (${Year})`}
</LimitedTextTitle>
<Tooltip placement='top' target={imdbID} isOpen={this.state.open} toggle={this.toggle}>
{Title}
</Tooltip>
</CardTitle>
to this:
<CardTitle>
<LimitedTextTitle id={`movie-${imdbID}`}>
{`${Title} - (${Year})`}
</LimitedTextTitle>
<Tooltip placement='top' target={`movie-${imdbID}`} isOpen={this.state.open} toggle={this.toggle}>
{Title}
</Tooltip>
</CardTitle>
You can avoid using state by simply switching to UncontrolledTooltip which handles all the toggle itself without asking you to handle that explicitly, like:
<CardTitle>
<LimitedTextTitle id={`movie-${imdbID}`}>
{`${Title} - (${Year})`}
</LimitedTextTitle>
<UncontrolledTooltip placement='top' target={`movie-${imdbID}`}>
{Title}
</UncontrolledTooltip>
</CardTitle>
Rendering dynamic content in tooltip in react js is very simple.
Use ReactTooltip.
For full understanding check below example.
Here I am adding requestId in tooltip as dynamically.
{
completedTransactions.map((item, id) => (
<tr key={id + 1}>
<td>{id + 1}</td>
<td>
<span data-tip={item.requestId} data-for="registerTip">
{item.TransactionId}
</span>
<ReactTooltip id="registerTip" place="top" />
</td>
<td>{item.groupName}</td>
<td>{item.purposeName}</td>
<td>{dateFormat(item.update, "dd-mm-yyyy hh:mm tt")}</td>
</tr>
));
}

Resources