React fullcalendar with tooltip - reactjs

I am trying to add a tooltip on hover when hovering over the event in fullcalendar. The alert works but the tooltip doesen't appear. Any tips to get me going?
const events = [
{
title: "Event 1",
start: "2021-10-04",
end: "2021-10-06",
},
{
title: "Event 2",
start: "2021-10-04",
}
];
export default function Calendar() {
return (
<div>
<FullCalendar
events={events}
eventMouseEnter={
(arg) => {
<ReactTooltip id="registerTip" place="top" effect="solid">
{arg.event.title}
</ReactTooltip>
// alert(arg.event.title);
}
}
plugins={[dayGridPlugin]}
/>
</div>
);
}
Example (Working example):
https://codesandbox.io/s/0m03n?file=/src/App.js:136-165

TL.DR: To add a tooltip to the whole calendar:
return (
<ReactTooltip id="registerTip" place="top" effect="solid">
<FullCalendar events={events} plugins={[dayGridPlugin]} />
<ReactTooltip />
);
To add a tooltip only to the title, you must use custom views components where your wrap the view with the tooltip: https://fullcalendar.io/docs/content-injection
For any component to show on the screen, it has to be rendered. On a very high level, that generally means that one component has to do return (<ComponentToRender />).
In your example, you are simply executing the code for the <ReactTooltip /> when hovering the calendar, not actually rendering the tooltip.
Pay attention that returning the <ReactTooltip /> on the onMouseEnter wouldn't work either. In that case you would be returning it on the callback, not on the component itself.
For your understanding, the <ReactTooltip /> probably has some internal logic that does something (on a very pseudo code level) like:
const [showTooltip, setShowTooltip] = useState();
onMouseEnter = setShowTooltip(true);
onMouseLeave = setShowTooltip(false);
...
return (
<>
{showTooltip && <Tooltip>}
{children}
</>

Related

Display tooltip with diferent values for each bar using recharts

I have a chart and I want to display a tooltip for each bar with its data.
I used this example from their documentation https://recharts.org/en-US/examples/CustomContentOfTooltip
I created a custom tooltip element
const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) =>{
console.log(payload)
if (active) {
return (
<div className="custom-tooltip">
{payload && payload[0] &&
<p className="label">{`${payload![0].name}: ${payload![0].value}`}</p>
}
</div>
);
}
return null;
};
and this is bar chart element where I use the custom tooltip as in the example above.
<BarChart>
...
<Tooltip cursor={false} content={<CustomTooltip />} />
{Object.values(dataKeyBar).map(({chart, color}, index) => {
return <Bar
radius={barSize}
key={`bar-${index}`}
barSize={barSize}
dataKey={chart}
fill={color}
/>
})}
</BarChart>
My problem is no matter what bar I'm hovering, I get the same data in my tooltip -
{`${payload![0].name}: ${payload![0].value}`}
the payload looks like this:
what I have tried to display different data in the tooltip for each bar is for the customTooltip but the tooltip is not showing at all.
{payload && payload.map((bar: any) => {
return <div className="custom-tooltip"><p className="label">{`${bar.name}: ${bar.value}`}</p></div>
})}
How can I display a tooltip with different data for each bar?
From what i'm seeing right now you are locating the Custom Tooltip component outside of map, so no matter where you hover over you'll get the same result sure, did you try to call it inside map ?

render different icons depending on boolean props inside a tooltip

so i have this situation which i have a bar that map over data and renders an boxes with some icons, and each of them has it boolean prop that defines if the icons should be rendered.
BoxBar.tsx map over some data and renders a single box component, like so:
const renderSingleBox = (item: any) => {
return (
<SingleBox
subsystem={item.subsystem}
serviceState={item.serviceState}
isSimulatorMode={item.isSimulatorMode}
/>
);
};
const renderIconBoxes = () => (
<Wrapper>{data.map(item => renderSingleBox(item))}</Wrapper>
);
SingleBox.tsx renders some icons,
like so:
const renderIconBox = () => {
return (
<IconBox>
<Tooltip message="Test" position={Position.Bottom} asPortal={true} cursor="pointer">
{isSimulatorMode && <SimulatorModeIcon />}
{isRemoteMode && <FontAwesomeIcon icon={faWifiSlash} size="sm" fontSize={16} />}
{isServiceMode && <ServiceIcon />}
{isResetIconVisible && (
<FontAwesomeIcon icon={faUndo} size="sm" fontSize={16} color={colors.primary} />
)}
</Tooltip>
</IconBox>
);
};
But i need the Tooltip component to display the text message corresponding to the prop name itself,
for example, if the prop isSimulatorMode is true and the icon is displayed, the Tooltip should render the message isSimulatorMode
I hope its clear what i'm trying to get here.
I was thinking of maybe create a Map, something like this:
const MapPropsToElements = new Map<string, JSX.Element>([
["isSimulatorMode", <SimulatorModeIcon />],
["isRemoteMode", <FontAwesomeIcon icon={faWifiSlash} size="sm" fontSize={16} />],
["isServiceMode", <ServiceIcon />],
[
"isResetIconVisible",
<FontAwesomeIcon icon={faUndo} size="sm" fontSize={16} color={colors.primary} />
]
]);
but still i don't know how to iterate on it and render it correctly.
any ideas?
can i create a dynamic map that renders icons ?
thanx
Try doing something like
const icons = [{name: 'isSimulatorMode', icon: <SimulatorModeIcon />}, {name: 'isServiceMode', icon: <ServiceIcon />}, ... ]

node.current shows up as null when using useRef(), despite using within useEffect hook

My goal is to large scrollable modal where if the item provided to the modal changes (there is a "More projects" section at the bottom which should change the modal content), the modal automatically scrolls to the top. Since I can't use the window object, other sources seem to indicate a ref is the best way to go. However, I keep getting the error node.current is undefined at the time of compiling.
I saw elsewhere that this should be avoidable by working with the ref within a useEffect hook, since this will ensure the ref has been initialized by the time it runs, however this is not happening here.
const PortfolioModal = ({
open,
handleClose,
item,
setItem,
...props
}) => {
const node = useRef();
useEffect(() => {
node.current.scrollIntoView()
}, [item]);
return (
<Dialog onClose={handleClose} open={open} fullWidth={true} maxWidth='lg'>
<div ref={node}></div>
<Content>
{a bunch of stuff is here}
<PortfolioFooter
backgroundImage={`${process.env.PUBLIC_URL}/images/backgrounds/panel5.png`}
item={item}
setItem={setItem}
/>
</FlexContainer>
</Content>
</Dialog>
)
};
EDIT: Additional note -- I initially wrapped the entire component with a div with a ref and tried to use scrollTop and I did not receive an error, but there was also no scrolling, so I thought I would try using scrollIntoView with an invisible element.
This is what worked for me, based on this post:
const nodeRef = useRef();
useEffect(() => {
if (nodeRef.current) {
nodeRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}, [item]);
return (
<Dialog onClose={handleClose} open={open} fullWidth={true} maxWidth='lg' scroll="body">
<div ref={node => {
nodeRef.current = node;
}}>
<Content>
{content}
</Content>
</div>
</Dialog>

can you set outer HTML in return function of a React component?

The default behavior of the MailchimpSubscribe component displays a status alert upon user signup. I want to move this status alert outside of it's position in the DOM so that it shows up at the top of the page.
My code looks like this:
import MailchimpSubscribe from "react-mailchimp-subscribe"
import SimpleForm from './SimpleForm.js'
function Info() {
return (
<div className="canary">
<MailchimpSubscribe url={process.env.REACT_APP_MAILCHIMP_URL}
render={({subscribe, status, message}) => <SimpleForm
status={status}
message={message}
className="form"
style={{}}
onSubmitted={formData => subscribe(formData)}
/>
}/>
</div>
);
}
export default Info;
To change the default button text, I create my own SimpleForm.js component:
import React from "react";
// a basic form
const SimpleForm = ({ status, message, className, style, onSubmitted }) => {
let input;
const submit = () =>
input &&
input.value.indexOf("#") > -1 &&
onSubmitted({
EMAIL: input.value
});
return (
<div className={className} style={style}>
{status === "sending" && <div style={{ color: "blue" }}>sending...</div>}
{status === "error" && (
<div
style={{ color: "red" }}
dangerouslySetInnerHTML={{ __html: message }}
/>
)}
{status === "success" && (
<div
style={{ color: "green" }}
dangerouslySetInnerHTML={{ __html: message }}
/>
)}
<input
ref={node => (input = node)}
type="email"
placeholder="Your email"
/>
<button onClick={submit}>Subscribe</button>
</div>
);
};
export default SimpleForm;
How would I modify this return function so that the dangerouslySetInnerHTML={{ __html: message }} part is set on <div className="canary">?
From what I see in component definition you can pass render props
MailchimpSubscribe.defaultProps = {
render: ({ subscribe, status, message }) => (
<SimpleForm
status={status}
message={message}
onSubmitted={formData => subscribe(formData)}
/>
)
};
with SimpleForm and include specific className style
<MailchimpSubscribe url={process.env.REACT_APP_MAILCHIMP_URL}
render={({subscribe, status, message}) => <SimpleForm
status={status}
message={message}
className="form"
style={{}}
onSubmitted={formData => subscribe(formData)}
/>
}/>
If you need more customization you can create your own component based on what's inside SimpleForm
This is how I understand the problem 😀:
Question
How do you render data passed as props to Component A within Component B?
MailchimpSubscribe holds message inside of its internal state and passes it as a prop to the results of its render function
Answer
React aims to only pass data from parents to children in unidirectional data flow,
so the data passed as props to MailchimpSubscribe cannot directly be rendered in another component which is not its child 😕
The best way I can think of to circumvent this is to leverage the Context API to create shared state between the SimpleForm rendered inside MailchimpSubscribe and a DisplayMessage rendered elsewhere in your app.
In a new file SharedState.js
Create SharedContext to allow components to access context
Create SharedProvider which is a parent that will make the context available to all its children
In SimpleForm
Read SharedContext
Set up an effect to push the status and message values up to the context. This effect will be called each time those values change.
return null when the status is not null, as you would like to render the message elsewhere and hide the form.
In DisplayMessage
Read SharedContext
Use status and message from SharedContext to render your message
Demo
I put together this CodeSandbox which illustrates this strategy.
References
React docs for useContext
React docs for Context
Hope that helps! Let me know if there's anything you'd like clarified further 👍

If statement which determines which React element to be rendered, weird behaviour

I created a Sprite component which takes in 'icon' as a prop and determines which svg to render but I'm experiencing some weird behaviour.
I've had to resort to this method because I haven't been able to find a way to work with svg's (how to change their fill color!)
const Sprite: React.SFC<ISpriteProps> = (props: ISpriteProps) => {
const color = props.color || '#ACACAC'
let icon
if (props.icon === 'pin') {
icon = <Pin color={color} />
} else if (
props.icon === 'profile'
) {
icon = <Profile color={color} />
}
return (
<React.Fragment>
{icon}
</React.Fragment>
)
}
export default Sprite
Using the <Sprite/> in my <Navigation/> component like so
<Nav styles={this.state.styles}>
<ProfileIcon onClick={this.onProfileSelected}>
<Sprite icon='profile'
color={this.state.viewing === 'profile' ? '#5A4DB2' : ''} />
</ProfileIcon>
<LogoIcon onClick={this.onLogoSelected}>
<h1>
<img src={logo} alt="zestly" />
</h1>
</LogoIcon>
<MessagesIcon>
<img src={chat} onClick={this.onMessageSelected}
alt="messages" />
</MessagesIcon>
</Nav>
and in my <CardBlock/> component like so
const Card: React.SFC<{}> = (place) => {
return (
<CardOuter>
<CardPhoto>
<span>
<Sprite icon='pin' color='#fff' />Fitzroy</span>
</CardPhoto>
<CardDetails>
<div>
<h3>Vegie bar</h3>
<h4>7:00pm</h4>
</div>
<ProfileIcons />
</CardDetails>
</CardOuter>
)
}
For some reason the icon prop I choose to pass in to the Navigation component Sprite is determining what is rendered for the Sprite's in CardBlock as well.
E.g if I make it 'profile' in Navigation it will make all sprites render the profile icon for Sprite as well even though I specifically pass in 'pin'. If I switch it to 'pin' in Navigation, all CardBlock sprites will render the pin icon.
I don't understand, is this some weird React render quirk?
EDIT: So I thought it was something to do with stateless functional component rendering so I changed Sprite to a Component
class Sprite extends React.Component<ISpriteProps, {}> {
public render() {
const color = this.props.color || '#ACACAC'
const icons = {
'profile': Profile,
'pin': Pin
}
const ActiveIcon = icons[this.props.icon]
return (
<ActiveIcon color={color} />
)
}
}
Still no love, it's rendering ALL of them as profile icons if I pass 'profile' as icon prop to Sprite in Navigation component.
EDIT: Solved. It was something wrong with the svg's
you could use classnames library to select what class should be added to React element, including svg. And in css you give fill rule that you want. https://github.com/JedWatson/classnames

Resources