How to pass a custom component to title props in MUI tooltip? - reactjs

I am trying to customize a Tooltip component by passing a custom component like the image.
At first time, I've used Popover once, but the main reason why I am going to use Tooltip is because of the arrow of Tooltip.
Basically, I am using React and MUI v5 running on TypeScript.
I built a custom Tooltip component like this.
<Tooltip
open={open}
arrow
placement={placement}
title={
<Box component="span" p={1} width={width}>
<IconButton size="small" onClick={onClose}>
<CloseIcon />
</IconButton>
<Box my={2} px={4}>
<Typography my={2}>
{message}
</Typography>
<FormControlLabel control={<Checkbox defaultChecked />} label="Don’t show again" />
</Box>
</Box>
}
{...rest}
>
{children}
</Tooltip>
It seems to be working, but it keeps saying an error message Tooltip title. Zero-length titles string are never displayed. 'title' is specified more than once, so this usage will be overwritten.ts(2783).
How can I resolve this issue, or should I use Popover rather than Tooltip component?

You need to move title prop after {...rest} parameters.
It looks like your rest paramaters already have title property inside, so basically you are overwriting your title with something else from rest parameters (at least typescript thinks that).
It is the same as:
const foo = { title: 'aaaa', sth: 'cccc' };
const bar = { title: 'bbbb', ...foo}
console.log(bar.title); // => 'aaaa'
If you open this code in some environment with typescript support, it will also show same error as yours, see
What you need to do is:
const foo = { title: 'aaaa', sth: 'cccc' };
const bar = { ...foo, title: 'bbbb' };
console.log(bar.title); // => 'bbbb'

Related

Mapping Horizontal divider using Material UI react

I'm working on menu component storybook where i have mapped icon & text , problem is i have horizontal divider in between , how do i Map it with icons & text.From below code i'm getting divider at the bottom of the Menu. I'm trying to achieve as it is in the image. In the storybook i have to map few menu variants as in mui, for that i cannot hardcode divider, if there is any way i can manage it with Map or any other method. Thanks.
export const Menu= ({ icons }) => {
return (
<Paper sx={{ width: 320, maxWidth: '100%' }} >
<MenuList>
{icons.map((item) => (
<MenuItem>
<ListItemIcon fontSize="small">{item.icon}</ListItemIcon>
<ListItemText>{item.label}</ListItemText>
<Typography variant="body2" color="text.secondary">{item.typography}</Typography>
</MenuItem>
))}
<Divider /> /* how do i map this */
</MenuList>
</Paper >
);
}
Stories.js
icons: [
{ icon: <ContentCut fontSize="small" />, typography: "⌘X", label: "Cut" },
{ icon: <ContentCopy fontSize="small" />, typography: "⌘C", label: "Copy" },
]
Answer:
If you want the divider to be just one then don't map over it . thats the purpose of the .map() method.
and To Acheive the required results i just removed <Menu></Menu> Component and Just Kept the <Papper></Papper> Component
Notes :
In terms of how to Map the Divider with the below example ,you can just wrap it in a empty react fragment<></> and map over the <MenuItem></MenuItem> .
Only issue is that youll get an error in your key props which will say its not unique it can be fixed by assigning index key like the example below and wrap the <MenuItem></MenuItem> Component in It. However thats not best practice ,
<React.Fragment></React.Fragment> Is better practice according to Keyed Fragment React 18 Docs to add a key prop However that's giving a New Error in MUI.
Thats not an issue since were mapping over the MenuItem Component , However if we use for example in #Nathan Comments <React.Fragment key={index}></React.Fragment> or my previous answer to use <></> we would be mapping over the React.Fragment Itself or the empty fragment and would get a new error MUI: The Menu component doesn't accept a Fragment as a child. Uncomment the Examples in the Sandbox Check the sandbox console.
Check Code Code SandBox
Solution
export const MenuIcon = ({ menuicons }) => {
return (
<Paper sx={{ width: 320, maxWidth: "100%" }}>
{menuicons.map((item, index) => (
<MenuItem key={item + index}>
<ListItemIcon fontSize="small">{item.icon}</ListItemIcon>
<ListItemText>{item.label}</ListItemText>
<Typography variant="body2" color="text.secondary">
{item.typography}
</Typography>
</MenuItem>
))}
<Divider />
<ListItemIcon fontSize="small">
ClipBoard <ContentCopyIcon fontSize="small" />
</ListItemIcon>
</Paper>
);
}
References
Empty Fragment Syntax React Docs
Stack OverFlow Question MUI Icons - as props
.map() method Syntax - MDN Docs

React - render component inside accordion that was created in loop

I'm looking for any smart way to acheive below scenario:
I have a component that creates accordion in loop by given array of data. Single accordion looks like:
<Accordion expanded={expanded === 'panel1'} onChange={handleChange('panel1')}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={"${id}-content"}
id={"${id}-header"}
>
<Typography sx={{ width: '33%', flexShrink: 0 }}>
{status}
</Typography>
<Typography sx={{ color: 'text.secondary' }}>
{rID}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
<BookingsDetails rID={rID} />
</Typography>
</AccordionDetails>
</Accordion>
And onChange func:
const handleChange = (panel) => (event, isExpanded) => {
setExpanded(isExpanded ? panel : false);
};
(I'm using Accordion from MUI.com)
Here is my problem. If I will have 1000+ records that will create 1000+ accordion's row during the loop, React will populate 1000+ times data in <BookingsDetails rID={rID} /> in advance that is not efficient.
What I want to acheive is:
Render <BookingsDetails rID={rID} /> component only once and only inside that accordion that was expanded by onChange method.
Now it's render inside each accordion which is wrong.
Can someone give me a tip how to do that?
Well you can render your component conditionally, depending on the expanded panel, so:
{expanded === "panel1" ? <BookingsDetails rID={rID} /> : undefined}
I also suggest to change your handleChange function in this way:
setExpanded(isExpanded ? panel : undefined); // Or an empty string
To avoid mixing a boolean with something that should be a string or something not defined.

React Mui DateRangePicker include Calendar Icon

I want to implement a DateRangePicker from Material UI with a calendar icon, e.g. it should look like that:
According to the Api documentation it should work with
components={{
OpenPickerIcon: CalendarTodayIcon
}}
but it doesn't. See codesandbock.
I also tried it by adding an Icon manually to the TextField which shows an Icon but doesn't open the PopupMenu when clicking on the Icon.
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton>
<CalendarTodayIcon />
</IconButton>
</InputAdornment>
)
}}
Anyone an idea how to implement that? I am using mui 5.1.0 and mui/lab 5.0.0-alpha.50
I haven't used the Component from the library but you're right according to the documentation
components={{
OpenPickerIcon: CalendarTodayIcon
}}
Should work but it doesn't.
In your workaround, you're not supplying anything on the IconButton onClick event handler so naturally, the icon does nothing when clicked.
What you need to do is focus on the input whenever the Icon is clicked. You can achieve that by using the useRef hook in your input and then calling the current.focus() method inside the onClick handler of the IconButton.
const startInputRef = React.useRef();
<TextField
inputRef={startInputRef}
{...startProps}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => {
startInputRef.current.focus();
}}
>
<CalendarTodayIcon />
</IconButton>
</InputAdornment>
)
}}
/>
See codesandbox for a working example.
I still think this is a hacky workaround though so I'd suggest opening an issue on the library's github repo to get instructions.

Pass icon button as props using react

I have a common grid component where I define the structure of the grid and also structure of a button bar that goes on top of the grid.
Common-grid.js
<Box height='100%' width='100%' position='absolute'>
<div className="common-grid">
<div className="button-bar">
</div>
<div className="ag-grid">
</div>
</div>
</Box>
I pass data to the grid from my other component based to fill in the grid.
MyComponent.js
{gridData.length > 0 && <Grid tableData={gridData} columnData={activeListColumnDef} {...props}/>}
Along with the data, I would also like to pass icon buttons that I would like to see in button bar.
<IconButton
icon={<AddIcon />}
onClick={onClickOpenActiveListEditor}
/>
<IconButton
icon={<EditIcon />}
/>
I do not want to define icon buttons in the common component but pass it as props. Is it possible to pass such html elements along with its event listeners as props? Please help!
Sure, it's called a render prop. Just directly pass the node like this:
// in the parent component
<Grid
tableData={gridData}
columnData={activeListColumnDef}
icon={<AddIcon onClick={onClickOpenActiveListEditor} />}
{...props}
/>
// in the Grid component
function Grid({tableData, columnData, icon}){
return (
<>
// grid stuff
{icon && icon}
</>
)
}
If you need typescript support, the node would typed as such:
interface GridProps{
// stuff
icon?: React.ReactNode;
}
You could do something like:
const renderIcon = (onClick) => {
return <Icon onClick={onClick} />
}
...
<IconButton renderIcon={renderIcon} />
Then, inside IconButton:
{renderIcon()}

Problem with adding a Border to the Header and Body of a material-table

I'm using material-table in my React App. I want to add left and right border to the table.
Here is the code for overriding Header and Body components.
components={{
Header: props => (
<Box ml={2} mr={2}>
<MTableHeader {...props}/>
</Box>
),
Body: props => (
<Box ml={2} mr={2}>
<MTableBody {...props}/>
</Box>
),
}}
When I try to add the borders, table header and body is distorted like this:
I also tried overriding the Table component with Paper but it looks like Paper is a wrapper to the actual table.

Resources