render different icons depending on boolean props inside a tooltip - reactjs

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 />}, ... ]

Related

How to pass color props by Nivo ResponsiveBar?

I use Nivo's responsive bar.
I want to show a bar like the image. And I can do it if I set the colors directly like below . But I can set it by passing the props or set by a function.
How can I do it?
<ResponsiveBar
colors='#0481F8'
/>
By a function
const getColor = () => title===A ? '#0481F8' : title===B ? '#F98700'
<ResponsiveBar
title={title}
data={data}
colors={getColor()}
/>
By passing props
Parent
<Chart color='#0481F8' data=...>
Child
const Chart = ({data, color}) => {
return (
<>
<ResponsiveBar
data={data}
colors={color}
/>
</>
)}
Yes you can set it through props, ResponsiveBar can take multiple colors also. You should use it as,
<Chart color={['#0481F8']} data={..}>
const Chart = ({data, color}) => {
return (
<ResponsiveBar
data={data}
colors={color}
/>
)}

Accessing a component state from a sibling button

I'm building a page that will render a dynamic number of expandable rows based on data from a query.
Each expandable row contains a grid as well as a button which should add a new row to said grid.
The button needs to access and update the state of the grid.
My problem is that I don't see any way to do this from the onClick handler of a button.
Additionally, you'll see the ExpandableRow component is cloning the children (button and grid) defined in SomePage, which further complicates my issue.
Can anyone suggest a workaround that might help me accomplish my goal?
const SomePage = (props) => {
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { /* Need to access MyGrid state */ }} />
Add Row
</button>
<MyGrid>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
const ExpandableRowsComponent = (props) => {
const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
return (
<>
{data.map((dataItem) => (
<ExpandableRow id={dataItem.id} />
))}
</>
);
};
const ExpandableRow = (props) => {
const [expanded, setExpanded] = useState(false);
return (
<div className="row-item">
<div className="row-item-header">
<img
className="collapse-icon"
onClick={() => setExpanded(!expanded)}
/>
</div>
{expanded && (
<div className="row-item-content">
{React.Children.map(props.children, (child => cloneElement(child, { id: props.id })))}
</div>
)}
</div>
);
};
There are two main ways to achieve this
Hoist the state to common ancestors
Using ref (sibling communication based on this tweet)
const SomePage = (props) => {
const ref = useRef({})
return (
<>
<MyPageComponent>
<ExpandableRowsComponent>
<button onClick={(e) => { console.log(ref.current.state) }} />
Add Row
</button>
<MyGrid ref={ref}>
<GridColumn field="somefield" />
</MyGrid>
</ExpandableRowsComponent>
</MyPageComponent>
</>
);
};
Steps required for seconds step if you want to not only access state but also update state
You must define a forwardRef component
Update ref in useEffect or pass your API object via useImerativeHandle
You can also use or get inspired by react-aptor.
⭐ If you are only concerned about the UI part (the placement of button element)
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
(Mentioned point by #Sanira Nimantha)

React fullcalendar with tooltip

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}
</>

Custom HTML Button wrapped in Link not working

I have made a custom Button component that returns HTML button. Now I am using this Button inside Link from next-routes. The problem is that it does not work this way. Weird thing is that button works correctly if I use HTML button inside Link. However, both button are being rendered in the DOM in exact same way. Following is the code:
// Button.js
import React from "react";
import classNames from "classnames";
const Button = ({
children,
newClass = "",
onClickHandler = () => { },
isSubmitting = false,
inlineBtn = true,
disabled,
primary,
secondary,
basic,
notCentered = true,
shaded,
miniLoader,
type = "button",
isTransparent,
fontClass = "",
small
}) => {
return (
<React.Fragment>
<button
className={classNames(
`${isTransparent ? 'btn-transparent' : 'btn'} ${fontClass} ${newClass}`,
{
"btn-block": !inlineBtn,
"col-mx-auto": !notCentered,
"btn-primary": primary,
"btn-secondary": secondary,
"btn-basic": basic,
shaded: shaded,
loading: isSubmitting,
"loading-sm": miniLoader,
"btn-sm": small
}
)}
disabled={disabled}
type={type}
onClick={onClickHandler}
>
<span>{children}</span>
</button>
</React.Fragment>
);
};
export { Button };
The following does not work:
<Link route="/register/location">
<Button basic small>
Sign Up
</Button>
</Link>
The following works fine:
<Link route="/register/location">
<button className="btn btn-basic btn-sm" type="button" onClick={() => { }}>
<span>Sign Up</span>
</button>
</Link>
You can update your Button component as the following.
const Button = ({
as = "button",
children,
newClass = "",
onClickHandler = () => {},
isSubmitting = false,
inlineBtn = true,
disabled,
primary,
secondary,
basic,
notCentered = true,
shaded,
miniLoader,
type = "button",
isTransparent,
fontClass = "",
small,
...rest
}) => {
const Wrapper = as;
return (
<Wrapper
className={classNames(
`${isTransparent ? "btn-transparent" : "btn"} ${fontClass} ${newClass}`,
{
"btn-block": !inlineBtn,
"col-mx-auto": !notCentered,
"btn-primary": primary,
"btn-secondary": secondary,
"btn-basic": basic,
shaded: shaded,
loading: isSubmitting,
"loading-sm": miniLoader,
"btn-sm": small
}
)}
disabled={disabled}
type={type}
onClick={onClickHandler}
{...rest}
>
<span>{children}</span>
</Wrapper>
);
};
This will allow custom Wrapper to be used for your Button component. And we pass every inherited props into your Wrapper so that your route props will be received in your Link component.
You can then use it like so
<Button basic small as={Link} route="/register/location">
Sign Up
</Button>
This uses the ES6 spread operator syntax. Basically you render your Button component as a Link component, and any inherited props will be passed to the Link component, hence route props is passed into Link component.
This follow the API Design Approach similar to Material-ui's spead approach. This will allow your component to be more flexible as well.

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