React - Why is componentDidMount event called instantly - javascript

I've playing around with animation implemented with reactjs.
In the app I created a car which drives around a track. On this track there are obstacles, which the car should recognize.
I'm using window.setInterval for the repeating events. Maybe this is not the best option, but actually I don't know how to do else.
Since some changes, there are multiple intervals running.
But actually I don't know the reason for it. Can anybody give me a hint, why the racer component is instantly running in componentdidmount event?
The Racer component is giving the current position and degree / ankle to the Track component. The Track component is storing these values in states and giving it to the Racer component as props. But this should not lead to instantly firing componentdidmount event of Racer component, or?
Here is my code:
App.js
import React, { Component } from 'react';
import Track from './components/track.js';
const uuidv1 = require('uuid/v1');
class App extends Component {
constructor(props) {
super(props);
this.state = {
obstacles: [
{
key: uuidv1(),
position: {
left: 500,
top:10,
},
width: 25,
height: 25,
},
{
key: uuidv1(),
position: {
left: 650,
top:60,
},
width: 25,
height: 25,
}
],
};
}
render() {
return (
<div className="App">
<Track width={800} height={100} obstacles={this.state.obstacles}>
</Track>
</div>
);
}
}
export default App;
Track.js
import React, { Component } from 'react';
import styled from 'styled-components';
import Racer from './racer.js';
import Obstacle from './obstacle';
import centralStrip from '../images/centralStrip.png';
const uuidv1 = require('uuid/v1');
class Track extends Component {
constructor(props) {
super(props);
this.state = {
racerCurrentPosition: {
top: 60,
left:150
},
racerDegree: 0,
};
}
componentDidMount() {
}
handleObstacleCheck(position, racerPosition) {
let obstacleFound = false;
obstacleFound = this.props.obstacles.map((obstacle) => {
let returnValue = false;
let obstacleRect = document.getElementById(obstacle.key).getBoundingClientRect();
if( position.right >= obstacleRect.left && position.right <= obstacleRect.right && racerPosition.top >= obstacleRect.top && racerPosition.bottom <= obstacleRect.bottom) {
returnValue = true;
}
return returnValue;
});
let isObstacleFound = false;
if(obstacleFound.indexOf(true) !== -1) {
isObstacleFound = true;
}
return isObstacleFound;
}
handleRacerPositionChange(position) {
this.setState({
racerCurrentPosition: position,
});
}
handleRacerDegreeChange(newDegree) {
this.setState({
racerDegree: newDegree,
});
}
render() {
return (
<TrackImage key={uuidv1()}
id="track"
width={this.props.width}
height={this.props.height}>
<Racer key={uuidv1()}
position={this.state.racerCurrentPosition}
onRacerPositionChange={this.handleRacerPositionChange.bind(this)}
degree={this.state.racerDegree}
onRacerDegreeChange={this.handleRacerDegreeChange.bind(this)}
obstacleFound={this.state.obstacleFound}
trackWidth={this.props.width}
trackHeight={this.props.height}
onObstacleCheck={this.handleObstacleCheck.bind(this)}
/>
{
this.props.obstacles.map((obstacle) => {
return (
<Obstacle key={obstacle.key}
id={obstacle.key}
position={obstacle.position}
width={obstacle.width}
height={obstacle.height}
/>
);
})
}
</TrackImage>
);
}
}
export default Track;
Racer.js
import React, { Component, Fragment } from 'react';
import styled from 'styled-components';
import HelperDistance from './helpers/distance.js';
import HelperCenterCar from './helpers/centerCar.js';
import racerImage from '../images/racer.png';
const uuidv1 = require('uuid/v1');
class Racer extends Component {
constructor(props) {
super(props);
this.state = {
key: uuidv1(),
intervalId: 0,
speed: 0,
helperForLeftPositioning: 0,
helperForTopPositioning: 0,
isMoving: false,
collision: false,
centerOfCarCoordinates: {
x: 25,
y: 12.5
},
obstacleFound: false,
};
this.start = this.start.bind(this);
this.move = this.move.bind(this);
}
componentDidMount() {
if(this.state.intervalId === 0) {
this.start();
}
}
componentWillUnmount() {
window.clearInterval(this.state.intervalId);
}
start() {
this.setState({
speed: 3,
isMoving: true,
}, () => {
this.createInterval();
});
}
stop() {
this.setState({
speed: 0,
isMoving: false,
}, () => {
window.clearInterval(this.state.intervalId);
});
}
move() {
if(this.state.obstacleFound === true) {
let newDegree;
if(this.props.degree === 0) {
newDegree = 360;
}
newDegree--;
this.props.onRacerDegreeChange(newDegree);
}
this.step();
}
step() {
if(this.state.isMoving) {
//...calculate new position
this.setState({
helperForTopPositioning: helperForTopPositioning,
helperForLeftPositioning: helperForLeftPositioning,
},() => {
let position = {
left: positionNewLeft,
top: positionNewTop
};
this.props.onRacerPositionChange(position);
});
}
}
createInterval = () => {
let intervalId = window.setInterval(() => {
this.move();
console.log("IntervalId: " + intervalId);
},100);
this.setState({
intervalId: intervalId,
})
}
handleDistanceChange(position) {
let racerRect = document.getElementById(this.state.key).getBoundingClientRect();
let obstacleFound = this.props.onObstacleCheck(position, racerRect);
if(this.state.obstacleFound !== obstacleFound) {
this.setState({
obstacleFound: obstacleFound
});
}
}
render() {
return (
<Fragment>
<Car key={this.state.key} id={this.state.key} position={this.props.position} degree={this.props.degree}>
<HelperCenterCar key={uuidv1()} position={this.state.centerOfCarCoordinates} degree={this.props.degree} />
<HelperDistance key={uuidv1()} onChange={this.handleDistanceChange.bind(this)} position={this.state.centerOfCarCoordinates} degree={this.props.degree} />
</Car>
</Fragment>
);
}
}
export default Racer;
The HelperCenterCar and HelperDistance are components, which helps to identify, if there is an obstacle in the way. I'll post just the code of HelperDistance, because here instantly state updates are fired.
HelperDistance.js
import React, { Component } from 'react';
import styled from 'styled-components';
const uuidv1 = require('uuid/v1');
class HelperDistance extends Component {
constructor(props) {
super(props);
this.state = {
key: uuidv1(),
};
}
componentDidMount() {
this.handleOnChange();
}
componentDidUpdate(prevProps, prevState, snapshot) {
this.handleOnChange();
}
handleOnChange() {
let position = document.getElementById(this.state.key).getBoundingClientRect();
this.props.onChange(position);
}
render() {
return (
<Line id={this.state.key} key={this.state.key} position={this.props.position} degree={this.props.degree} />
);
}
}
export default HelperDistance;

Related

react-hotkeys cntrl+s while focus is in textarea

I am trying to be able to use cntrl+s while focus within a textarea using react-hotkeys.
this.keyMap = {
KEY: "ctrl+s"
};
this.handlers = {
KEY: (e) => {
e.preventDefault();
this.saveBtn(c);
}
};
<HotKeys keyMap={this.keyMap} handlers={this.handlers}>
<textarea/>
</HotKeys>
You need to use Control+s, not ctrl+s.
You need to call configure like that so it won't ignore textareas:
import { configure } from "react-hotkeys";
configure({
ignoreTags: []
});
Following is not solution it's work around but it fulfills the requirement...
[Please Note] Basically I have restricted access to Ctrl key in browser and then it
works fine though.
import { HotKeys } from 'react-hotkeys';
import React, { PureComponent, Component } from 'react';
import { configure } from 'react-hotkeys';
const COLORS = ['green', 'purple', 'orange', 'grey', 'pink'];
const ACTION_KEY_MAP = {
KEY: 'Control+s',
};
class Login extends Component {
constructor(props, context) {
super(props, context);
this.changeColor = this.changeColor.bind(this);
configure({
ignoreTags: ['div']
});
this.state = {
colorNumber: 0
};
}
changeColor(e) {
e.preventDefault();
this.setState(({ colorNumber }) => ({ colorNumber: colorNumber === COLORS.length - 1 ? 0 : colorNumber + 1 }));
}
KeyDown(e){
if(e.ctrlKey) e.preventDefault();
}
render() {
const handlers = {
KEY: this.changeColor
};
const { colorNumber } = this.state;
const style = {
width: 200,
height: 60,
left: 20,
top: 20,
opacity: 1,
background: COLORS[colorNumber],
};
return (
<HotKeys
keyMap={ACTION_KEY_MAP}
handlers={handlers}
>
<textarea
style={style}
className="node"
tabIndex="0"
onKeyDown={this.KeyDown}
></textarea>
</HotKeys>
);
}
}
export default Login;

React - Slide out component on unmount

I want to slide out a React component when it umounts. I am using CSSTransition for the animation which works great for mounting, but not unmounting. Somehow I need to delay the unmount process. All the off-the-shelf solutions sadly do not work for me. I am removing an element by doing a post request and then actually removing it in the UI with a SignalR callback.
To make my sequence more clear, I created a sequence diagram:
This is my code right now:
Board.tsx
import React from 'react';
import { Config } from 'util/config';
import { container } from 'tsyringe';
import { AppState } from 'store';
import { connect } from 'react-redux';
import { BoardState } from 'store/board/types';
import { BoardHubService } from 'services/hubs/boardHub.service';
import { BoardElementViewModel } from 'models/BoardElementViewModel';
import { BoardViewModel } from 'models/BoardViewModel';
import { BoardElement } from './boardElement/boardElement';
import { HttpService } from 'services/http.service';
import { setActiveBoard } from 'store/board/actions';
import './board.scss'
import { mapToType } from 'helpers/helpers';
import { TransitionGroup } from 'react-transition-group';
interface BoardProps {
activeBoardState: BoardState;
setActiveBoard: typeof setActiveBoard;
}
interface LocalBoardState {
boardElements: Array<BoardElementViewModel>
}
class Board extends React.Component<BoardProps, LocalBoardState> {
private config: Config;
private httpService: HttpService;
private boardHubService: BoardHubService;
constructor(props: any) {
super(props);
this.config = container.resolve(Config);
this.boardHubService = container.resolve(BoardHubService);
this.httpService = container.resolve(HttpService);
this.state = {
boardElements: []
}
}
async componentDidMount() {
// If there was any active board on page load...
if (this.props.activeBoardState.boardId) {
await this.loadBoardElements();
}
this.boardHubService.getConnection().on('SwitchedBoard', (response: BoardViewModel | null) => {
console.log(response);
this.setState({
boardElements: (response) ? response.elements : []
});
this.updateSiteTitle(response);
});
this.boardHubService.getConnection().on('ReceiveElement', (response: BoardElementViewModel) => {
let elements = this.state.boardElements;
elements.unshift(response);
this.setState(() => ({
boardElements: elements
}))
});
this.boardHubService.getConnection().on('RemoveElement', (response: string) => {
let elements = this.state.boardElements;
let element = mapToType<BoardElementViewModel>(elements.find(x => x.id === response));
elements.splice(elements.indexOf(element), 1);
this.setState(() => ({
boardElements: elements
}))
});
}
/**
* Load the elements from the board that was already active on page load.
*/
private async loadBoardElements() {
await this.httpService.getWithAuthorization<Array<BoardElementViewModel>>(`/boards/${this.props.activeBoardState.boardId}/elements`)
.then((response: Array<BoardElementViewModel>) => {
this.setState({
boardElements: response
});
})
.catch((e) => console.warn(e));
}
private updateSiteTitle(board: BoardViewModel | null) {
if (board != null) {
document.title = `${board.name} | ${this.config.siteName}`;
}
else {
document.title = this.config.siteName;
}
}
render() {
return (
<>
{this.props.activeBoardState.boardId != null
?
<div className="board-elements">
{this.state.boardElements.map((element: BoardElementViewModel, index) => {
return (
<BoardElement
key={index}
id={element.id}
// TODO: Use number from server
number={element.elementNumber}
user={element.user}
direction={element.direction}
note={element.note}
imageId={element.imageId}
createdAt={element.createdAt}
/>
)
})}
</div>
:
<div className="select-board-instruction">
<h1>Please select or create a board.</h1>
</div>
}
</>
)
}
}
const mapStateToProps = (state: AppState) => ({
activeBoardState: state.activeBoard
});
export default connect(mapStateToProps, { setActiveBoard })(Board);
BoardElement.tsx
import React from 'react';
import { UserViewModel } from 'models/UserViewModel';
import { Direction } from 'models/Direction';
import './boardElement.scss';
import { dateToReadableString } from 'helpers/helpers';
import { Config } from 'util/config';
import { container } from 'tsyringe';
import { HttpService } from 'services/http.service';
import $ from 'jquery'
import { CSSTransition } from 'react-transition-group';
interface BoardElementProps {
id: string;
number: number;
user: UserViewModel;
// TODO: Use direction Enum
direction?: Direction;
note?: string;
imageId?: string;
createdAt: Date;
}
interface BoardElementState {
show: boolean;
}
export class BoardElement extends React.Component<BoardElementProps, BoardElementState> {
private config: Config;
private httpService: HttpService;
private ref: any;
constructor(props: BoardElementProps) {
super(props);
this.state = {
show: false,
}
this.config = container.resolve(Config);
this.httpService = container.resolve(HttpService);
}
getReadableDirection(direction: Direction) {
// TODO: Richtingen vertalen
switch (direction) {
case Direction.North: return 'Noord';
case Direction.NorthEast: return 'Noordoost';
case Direction.East: return 'Oost';
case Direction.SouthEast: return 'Zuidoost';
case Direction.South: return 'Zuid';
case Direction.SouthWest: return 'Zuidwest';
case Direction.West: return 'West';
case Direction.NorthWest: return 'Noordwest';
}
}
removeElement() {
this.httpService.deleteWithAuthorization(`/boards/elements/${this.props.id}`).then(() => {
}, (error) => {
console.warn(error);
});
}
componentDidMount() {
setTimeout(() => {
this.setState(() => ({
show: true
}));
}, 500);
}
componentWillUnmount() {
this.setState(() => ({
show: false
}));
}
render() {
return (
<CSSTransition in={this.state.show} timeout={200} classNames={{
enter: 'animation-height',
enterDone: 'animation-height',
exit: ''
}}>
<div className="animation-wrapper animation-height-0" >
<div className="board-element" >
<div className="board-element-header">
<span className="board-element-number">{this.props.number}</span>
<span className="board-element-creator">{this.props.user.username}</span>
<i className="fas fa-trash ml-auto delete-icon" onClick={() => this.removeElement()}></i>
</div>
<div className="board-element-body">
{this.props.imageId
? <img className="board-element-image" src={`${this.config.apiUrl}/content/${this.props.imageId}`} />
: <p className="board-element-message">{this.props.note}</p>
}
</div>
<div className="board-element-footer">
{this.props.direction &&
<div className="board-element-direction">
<i className="fas fa-location-arrow direction mr-2"></i>{this.getReadableDirection(this.props.direction)}
</div>
}
<time className="board-element-timestamp" dateTime={this.props.createdAt.toString()}>{dateToReadableString(this.props.createdAt)}</time>
</div>
</div>
</div>
</CSSTransition >
)
}
}
For better illustration take a look at this GIF:
https://gyazo.com/3c933851ecec39029f25d4df3a136c2a
That is using jQuery in another project of mine. That is what I want to achive in React.
You can delay unmounting the component. Write a hoc and use a setTimeout. Maintain a state say shouldRender.
hoc
function delayUnmounting(Component) {
return class extends React.Component {
state = {
shouldRender: this.props.isMounted
};
componentDidUpdate(prevProps) {
if (prevProps.isMounted && !this.props.isMounted) {
setTimeout(
() => this.setState({ shouldRender: false }),
this.props.delayTime
);
} else if (!prevProps.isMounted && this.props.isMounted) {
this.setState({ shouldRender: true });
}
}
render() {
return this.state.shouldRender ? <Component {...this.props} /> : null;
}
};
}
usage
function Box(props) {
return (
<BoxWrapper isMounted={props.isMounted} delay={props.delay}>
✨🎶✨🎶✨🎶✨🎶✨
</BoxWrapper>
);
}
const DelayedComponent = delayUnmounting(Box);
See complete code in the demo
Read this article on medium

Warning react : setState(...): Can only update a mounted or mounting component

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
This is a react application, where a banner is fixed on the screen and passing random images. The way it was written is generating the warning in question.
import React from "react";
import Lightbox from "react-image-lightbox";
import logo from "./logo.png";
class Banner extends React.Component {
constructor(props) {
super(props);
this.state = {
images: [],
currentImage: logo,
isOpen: false,
sidebarOpen: true
};
}
async componentWillMount() {
await this.getBanners();
this.setState({ currentImage: this.state.images[0].url });
setInterval(async () => {
await this.getBanners();
}, 300000);
let i = 0;
setInterval(
() => {
this.setState({ currentImage: this.state.images[i].url });
if (i >= this.state.images.length - 1) {
i = 0;
} else {
i++;
}
},
10000,
i
);
}
async getBanners() {
const data = await (await fetch("/api/banners/active")).json();
if (data.true) {
this.setState({ images: data.true });
}
}
render() {
const { isOpen } = this.state;
return (
<div>
{isOpen && (
<Lightbox
mainSrc={this.state.currentImage}
onCloseRequest={() => this.setState({ isOpen: false })}
/>
)}
<footer>
<a>
<img
width={270}
height="200"
src={this.state.currentImage}
onClick={() => this.setState({ isOpen: true })}
alt="idk"
/>
</a>
</footer>
</div>
);
}
}
export default Banner;
Could anyone help improve this code?
You can put the numbers returned from setInterval on your instance and stop the intervals with clearInterval in componentWillUnmount so that they won't continue to run after the component has been unmounted.
class Banner extends React.Component {
constructor(props) {
super(props);
this.bannerInterval = null;
this.currentImageInterval = null;
this.state = {
images: [],
currentImage: logo,
isOpen: false,
sidebarOpen: true
};
}
async componentDidMount() {
await this.getBanners();
this.setState({ currentImage: this.state.images[0].url });
this.bannerInterval = setInterval(async () => {
await this.getBanners();
}, 300000);
let i = 0;
this.currentImageInterval = setInterval(
() => {
this.setState({ currentImage: this.state.images[i].url });
if (i >= this.state.images.length - 1) {
i = 0;
} else {
i++;
}
},
10000,
i
);
}
componentWillUnmount() {
clearInterval(this.bannerInterval);
clearInterval(this.currentImageInterval);
}
// ...
}
Use this template for any class-based component that has a state:
forgot about setState(), and use setComponentState declared down:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// other fields...
isUnmounted: false,
};
}
componentWillUnmount() {
this.setState({ isUnmounted: true });
}
setComponentState = (values) => {
if (!this.state.isUnmounted) this.setState(values);
};
}

React-dnd not working in Cordova on iOS

I'm using the react-dnd-touch-backend.
I'm able to get my DragSources to drag correctly, but the DropTargets don't accept them (or react to being dragged over).
The application only uses one wrapper component for each role (DragSource and DropTarget). I have also defined a custom drag layer. The drag/drop worked fine before adding the custom drag layer except my DragSources were invisible on iOS (which is why I added the drag layer in the first place), but now I can see the DragSources, but the DropTargets don't work.
Any help is much appreciated.
DragSource:
import React from "react";
import cn from "util/cn";
import {isCordova} from "util/detect-platform";
import {DragSource} from "react-dnd";
import {getEmptyImage} from "react-dnd-html5-backend";
require("./style.scss");
const TYPE = "DRAG-CONTAINER";
const source = {
beginDrag({value, left, top, children, DragPreviewComponent}) {
return {value, left, top, children, DragPreviewComponent};
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging()
};
}
function getStyles(props) {
const {left, top, isDragging} = props;
const transform = `translate3d(${left}px, ${top}px, 0)`;
return {
transform: transform,
WebkitTransform: transform,
opacity: isDragging ? 0 : 1
};
}
#DragSource(TYPE, source, collect)
export default class DragContainer extends React.Component {
static propTypes = {
value: React.PropTypes.any
};
static defaultProps = {style: {}};
componentDidMount() {
if(!isCordova()) {
this.props.connectDragPreview(getEmptyImage(), {
captureDraggingState: true
});
}
}
render() {
const {className, isDragging, connectDragSource, style} = this.props;
const classNames = cn(
"Drag-container",
isDragging ? "Drag-container--dragging" : null,
className
);
return connectDragSource(
<div {...this.props} className={classNames} value={null} style={{...style, ...getStyles(this.props)}}/>
);
}
}
DropTarget:
import React from "react";
import {DropTarget} from "react-dnd";
import cn from "util/cn";
require("./style.scss");
const TYPE = "DRAG-CONTAINER";
const target = {
drop(props, monitor) {
const {onDrop} = props;
const {value} = (monitor.getItem() || {value: null});
if(typeof onDrop === "function") {
setTimeout(() => onDrop(value), 100);
}
}
};
function collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver()
};
}
#DropTarget(TYPE, target, collect)
export default class DropContainer extends React.Component {
static propTypes = {
onDrop: React.PropTypes.func
};
render() {
const {connectDropTarget, isOver, className} = this.props;
const classNames = cn("Drop-container", isOver ? "Drop-container--over" : null, className);
return connectDropTarget(
<div {...this.props} className={classNames} onDrop={null} onDragEnter={null} onDragExit={null}/>
);
}
}
Custom Drag Layer:
import React from "react";
import {DragLayer} from "react-dnd";
const layerStyles = {
position: "fixed",
pointerEvents: "none",
width: "100%",
height: "100%",
zIndex: 100,
left: 0,
top: 0
};
function getItemStyles(props) {
const { initialOffset, currentOffset } = props;
if (!initialOffset || !currentOffset) {
return {
display: 'none'
};
}
let { x, y } = currentOffset;
if (props.snapToGrid) {
x -= initialOffset.x;
y -= initialOffset.y;
[x, y] = snapToGrid(x, y);
x += initialOffset.x;
y += initialOffset.y;
}
const transform = `translate(${x}px, ${y}px)`;
return {
transform: transform,
WebkitTransform: transform
};
}
#DragLayer(monitor => ({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialOffset: monitor.getInitialSourceClientOffset(),
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging()
}))
export default class CustomDragLayer extends React.Component {
render() {
const {item, itemType, isDragging} = this.props;
if (!isDragging || !item) return null;
const {DragPreviewComponent} = item;
if(!DragPreviewComponent) return null;
return (
<div style={layerStyles}>
<div style={getItemStyles(this.props)}>
<DragPreviewComponent {...item}/>
</div>
</div>
);
}
}
The problem was this issue in the react-dnd-touch-backend library:
https://github.com/yahoo/react-dnd-touch-backend/issues/34
Rolling back to version 0.2.7 fixed the issue.

How to create multiple instances of popper?

In my app, I'm trying to use Popper to create a tooltip over every element in the app.
(Usually, I would only show a single tooltip, but for a presentation I want to show more than one).
I wrote this utility Component to attach tooltip directly to ref.
It works pretty well, but when I try to use it inside an [].map() like regular react components, I lose all my positioning:
https://bit.dev/bit/base/atoms/ref-tooltip?example=5e81d946443f4900195606b7
import React, { Component } from 'react';
import { RefTooltip } from '#bit/bit.base.atoms.ref-tooltip'
export default class ExampleUsage extends Component {
state = { ref: [] };
handleRef = (elem) => {
if (this.state.ref.some(x => x === elem)) return;
this.setState({ ref: [elem] });
}
render() {
return (
<div>
<span ref={this.handleRef}>target</span>
{ /*
* (!)
* This .map() breaks tooltip
*
*/ }
{this.state.ref.map((elem, idx) => (
<RefTooltip key={idx} targetElement={elem}>
"tooltip"
</RefTooltip>
))}
</div>
);
}
}
//ref-tooltip.tsx
import React, { Component } from 'react';
import classNames from 'classnames';
//#ts-ignore
import createRef from 'react-create-ref';
import { createPopper, Instance, Options } from '#popperjs/core';
import styles from './ref-tooltip.module.scss';
export type RefTooltipProps = {
targetElement?: HTMLElement;
popperOptions?: Partial<Options>;
} & React.HTMLAttributes<HTMLDivElement>;
export class RefTooltip extends Component<RefTooltipProps> {
private ref = createRef();
private popperInstance?: Instance;
componentWillUnmount() {
this.destroy();
}
componentDidUpdate(prevProps: RefTooltipProps) {
const nextProps = this.props;
if (prevProps.targetElement !== nextProps.targetElement) {
this.reposition(nextProps.targetElement);
}
}
private reposition = (targetElement?: HTMLElement) => {
const { popperOptions = popperDefaultOptions } = this.props;
const popperElement = this.ref.current;
if (!targetElement) {
this.destroy();
}
if (!targetElement || !popperElement) return;
this.popperInstance = createPopper(targetElement, popperElement, popperOptions);
};
private destroy() {
if (!this.popperInstance) return;
this.popperInstance.destroy();
this.popperInstance = undefined;
}
render() {
const { className, targetElement, ...rest } = this.props;
return (
<div
{...rest}
ref={this.ref}
className={classNames(styles.tooltipWrapper, className)}
data-ignore-component-highlight
/>
);
}
}
const popperDefaultOptions: Partial<Options> = {
placement: 'top',
modifiers: [
{
name: 'flip',
enabled: false,
},
],
};
Expected:
Actual:
I don't understand why the .map() breaks popper. At least for an array of 1, it should behave the same.
Any ideas why this isn't working?

Categories

Resources