OverlayTrigger Tooltip Not Showing - javascript

Created codesandbox to show issue:
https://codesandbox.io/s/agitated-https-2xjs2?fontsize=14&hidenavigation=1&theme=dark
I'm wanting a Tooltip to show whenever I'm hovering over a card. I need to pass in a value from the OverlayTrigger to the Tooltip component. With the following code, nothing is showing when hovering over a card:
Character.js:
import React from 'react'
import { Card, OverlayTrigger } from 'react-bootstrap'
import Infott from '../components/Infott'
const Character = ({ character }) => {
return (
<OverlayTrigger
trigger='hover'
placement='bottom'
overlay={<Infott test={'Test'} />}
>
<Card className='my-3 py-3 rounded'>
<a href={`/character/${character._id}`}>
<Card.Img src={character.image} />
</a>
</Card>
</OverlayTrigger>
)
}
export default Character
Infott.js:
import React from 'react'
import { Tooltip } from 'react-bootstrap'
const Infott = ({ test }) => {
return (
<Tooltip id='character-tooltip' placement='bottom'>
<strong>{test}</strong>
</Tooltip>
)
}
export default Infott
If I add className=show to the Tooltip component, it will show and the test value is passed, but it's no longer placed next to the card but rather the bottom left of the webpage. My guess is the OverlayTrigger and Tooltip are not on the same page.
I can get the Tooltip placement showing and in the correct placement if I change overlay to overlay={Infott} and then change my Tooltip component to
const Infott = (props) => {
return (
<Tooltip id='character-tooltip' placement='bottom' {...props}>
<strong>{test}</strong>
</Tooltip>
)
}
But then I'm not able to pass the test value that I need.

OverlayTrigger seems to use ref to the target Tooltip for some actions as well as some properties that it injects so in order to work properly you must forward a ref from your custom component to the target Tooltip you have wrapped inside.
So the correct solution for having your Tooltip wrapped in a custom component should be something like:
const Infott = React.forwardRef(({test, ...props}, ref) => {
return (
<Tooltip id='character-tooltip' ref={ref} placement='bottom' {...props}>
<strong>{test}</strong>
</Tooltip>
);
});

Related

How to notify List component that its row item height has changed and make the row rerenders?

I have a react-virtualized List, each item in the rows have an expand button, when the button is clicked the height of the row changes and it expands and show more info.
the issue here is, after the button got clicked the inner Grid (ReactVirtualized__Grid__innerScrollContainer) CSS height property is not updating or the Gid is not re-rendering/notified of the changes in row height
What should I do to make it rerender the row everytime the row item height gets updated?
here is my main index.js file:
import React, {
cloneElement,
Component,
useMemo,
useCallback,
useEffect,
createRef,
useRef,
useState
} from "react";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import {
List,
AutoSizer,
CellMeasurer,
CellMeasurerCache
} from "react-virtualized";
import Expander from "./expander.js";
export default function Test() {
// List data as an array of strings
let list = [
"Brian Vaughn",
"Bob Smith",
"Someone Else",
"I hate making up names"
// And so on...
];
const listRef = useRef();
const expander = (
<Expander onChanged={() => listRef.current?.list?.forceUpdateGrid()} />
);
return (
<div>
<AutoSizer
// disableWidth
// disableHeight
>
{({ width, height }) => {
return (
<List
ref={listRef}
className="List"
autoHeight
width={800}
height={400}
rowCount={list.length}
rowHeight={30}
rowRenderer={({ index, isScrolling, key, style }) => (
<div className="Row" key={key} style={style}>
{list[index]}
{expander}
</div>
)}
/>
);
}}
</AutoSizer>
</div>
);
}
Here is my expander.js file ( Row Component ):
import React, {
cloneElement,
Component,
useMemo,
useCallback,
useEffect,
createRef,
useRef,
useState
} from "react";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
export default function Expander(props) {
// State : Declare
const [isClicked, setIsClicked] = useState(false);
const renderMoreText = () => {
if (isClicked) {
return (
<div style={{ backgroundColor: "red", height: 200 }}>
long lorem ipsum{" "}
</div>
);
}
return null;
};
useEffect(() => {
props.onChanged();
}, [isClicked]);
return (
<div>
<button
onClick={() => {
setIsClicked(true);
}}
>
{" "}
Expand more text{" "}
</button>
{renderMoreText()}
</div>
);
}
Here is my sandbox:
https://codesandbox.io/s/agitated-pond-xq1csq?file=/pages/index.js
I'm not familiar with react-virtualized, but reading the docs you could make use of the List onChanged event together with Expanders recomputeRowHeights().
Updated sandbox (quick and dirty): https://codesandbox.io/s/charming-cookies-omi14k?file=/pages/index.js

ClickAwayListener not working with Collapse or Fade transitions

I'm trying to create a notifications area. I show a notification icon, and when the user clicks on it, I show the list of notifications.
Here's a codesandbox
The problem is that I can't mix it with ClickAwayListener.
When I use ClickAwayListener it's not shown at all.
How should I fix this?
HeaderAction.js
import Tooltip from "#material-ui/core/Tooltip";
import Fade from "#material-ui/core/Fade";
import Collapse from "#material-ui/core/Collapse";
import React, { useState } from "react";
import ClickAwayListener from "#material-ui/core/ClickAwayListener";
import Icon from "#material-ui/core/Icon";
const HeaderAction = ({ icon, title, component }) => {
const Component = component || (() => <div>NA</div>);
const [showComponent, setShowComponent] = useState(false);
const handleClick = () => {
setShowComponent(!showComponent);
};
return (
<>
<Tooltip title={title || ""}>
<div onClick={() => handleClick()}>
<Icon>{icon}</Icon>
</div>
</Tooltip>
{/* This part is not working */}
{/* <ClickAwayListener onClickAway={() => setShowComponent(false)}>
<div>
<Fade in={showComponent}>
<div>
<Component />
</div>
</Fade>
</div>
</ClickAwayListener> */}
<Fade in={showComponent}>
<div>
<Component />
</div>
</Fade>
</>
);
};
export { HeaderAction };
When you click the icon button, handleClick is called and the showComponent state is set to true, but then onClickAway from ClickAwayListener is also called and set the showComponent state to false again. The fix is simple, don't let the onClickAway handler execute by stopping the propagation after clicking the button:
<div
onClick={(e) => {
e.stopPropagation();
handleClick();
}}
>

How to make Gatsby Link receive props as parameter and turn off click cursor

I have a component wrapper to cut some repetitive code on my application.
This wrapper has some props, so I can choose if I want to print a text with an icon or an icon that is a link to another page.
My objective is to render text with an icon and the icon is not clickable as a link, but if I render just an icon the icon is clickable as link.
rowListComponent:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import PeopleIcon from "#material-ui/icons/People";
import { Link } from "gatsby";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
import Button from "#material-ui/core/Button";
export default function RowListComponent(props) {
const style = styles();
return (
<List className={style.listSize}>
<ListItem className={style.listSize}>
<Link to={`/${props.component}/${props.link}`}>
<ListItemIcon>{props.icon}</ListItemIcon>
</Link>
<ListItemText>{props.name}</ListItemText>
</ListItem>
</List>
);
}
I want to use this component wrapper on another component and i use it like this:
return (
<div>
<MaterialTable
icons={tableIcons}
title={<h1 className={style.title}>Users</h1>}
columns={[
{
title: "Name",
field: "name",
render: rowData => (
<RowListComponent
icon={<PeopleIcon className={style.iconColor} />}
name={rowData.name}
/>
)
},
{
title: "Details",
field: "details",
render: rowData => (
<RowListComponent
icon={<ListAltIcon className={style.iconColor} />}
component={"users"}
link={rowData.details}
/>
)
}
]}
data={state.users}
options={{
search: true
}}
/>
</div>
);
My problem is that I want to make the link active or not with a prop as well, so when you pass the mouse over the link the cursor does not change.
UPDATED:
I come with a solution but it's not what I want but it works.
export default function RowListComponent(props) {
const style = styles();
return (
<List className={style.listSize}>
<ListItem className={style.listSize}>
<Link
to={`/${props.component}/${props.link}`}
className={props.isActive}
>
<ListItemIcon>{props.icon}</ListItemIcon>
</Link>
<ListItemText>{props.name}</ListItemText>
</ListItem>
</List>
);
}
I set the isActive prop: isActive = {style.disabledLink} and this style is pointerEvents: "none" but what i really wanted was just to say isActive = {true or false}
I think you want something like this:
<Link to={`/${props.component}/${props.link}`} className={`${props.isActive ? "is-active" : "not-active"}`}>
Now you can add css to the 'is-active' class that sets 'pointerEvents' to none. And you can pass a bool value to 'isActive'.
I have also seen this nice package to join conditional classnames. Then this would be the solution:
<Link to={`/${props.component}/${props.link}`} className={classNames({ 'is-active': props.isActive })}>
Or if you want to directly add the style without a classname:
<Link to={`/${props.component}/${props.link}`} style={{ pointerEvents: props.isActive ? 'none': 'auto'}}>

When moving JSX content to separate file it doesn't render properly

Let say I have this:
// Parent component:
export default function() {
return (
<React.Fragment>
<Toolbar slot="fixed" bottom>
This will be fixed to bottom of page
{order.name}
</Toolbar>
</React.Fragment>
)
}
I want to make the parent component with the least of code possible - shared
into small cuts, so the toolbar will be in a child component so it will be
looking just like this:
// Parent component:
export default function() {
return (
<React.Fragment>
<MyAwesomeToolbar order={order} />
</React.Fragment>
)
}
// MyAwesomeComponent:
export default function(self) {
let { order } = self.props
return (
<Toolbar slot="fixed" bottom>
This will be fixed to bottom of page
{order.name}
</Toolbar>
)
}
In the first example - when the toolbar is actually hard coded in the parent component,
everything works good - the toolbar lays in the bottom of the page.
But when doing it the second way, the toolbar lays just not in the bottom but
float in the middle of the page without the fixed attribute as well.
I have tried to make a component using class, or just a simple render file (.JSX).
Both didn't work.
How to render child component with the same properties and styles as it was layed in the parent?
Does it work if you move the <React.Fragment />?
// Parent component:
export default function() {
return (
<MyAwesomeToolbar order={order} />
);
}
// MyAwesomeComponent:
export default function(self) {
let { order } = self.props;
return (
<React.Fragment>
<Toolbar slot="fixed" bottom>
This will be fixed to bottom of page
{order.name}
</Toolbar>
</React.Fragment>
);
}

Tracking visibility of items rendered by react-window

I was looking for a way to know which list item is visible on the screen when using react-window .
The isVisible prop is returning the visibility wrongly .
https://codesandbox.io/s/bvaughnreact-window-fixed-size-list-vertical-nmukq
import React from "react";
import { render } from "react-dom";
import { FixedSizeList as List } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import TrackVisibility from "react-on-screen";
import "./styles.css";
const Row = ({ index, style, isVisible }) => (
<div className={index % 2 ? "ListItemOdd" : "ListItemEven"} style={style}>
Row {index + " is" + isVisible}
</div>
);
const RowWrapper = ({ index, style }) => (
<TrackVisibility>
<Row index={index} style={style} />
</TrackVisibility>
);
const Example = () => (
<AutoSizer>
{({ height, width }) => (
<List
className="List"
height={height}
itemCount={1000}
itemSize={35}
width={width}
>
{RowWrapper}
</List>
)}
</AutoSizer>
);
render(<Example />, document.getElementById("root"));
This could possibly be because of caching of items, but i was wondering if there is another way to track visibility of an item
I figured it out on my own . Please find the code sandboxes at
DynamicSizeList - https://codesandbox.io/s/piecykreact-window-dynamic-size-list-vertical-pooy2
By trapping the onWheel event and comparing the "tops" with clientHeight and ListHeight
FixedSizeList -https://codesandbox.io/s/bvaughnreact-window-fixed-size-list-vertical-nmukq
Also based on onWheel event.
It doesnt work with react-on-screen .
If someOne knows a better way of doing then please answer.

Categories

Resources