AG Grid custom detail for master detail view not rendering 'could not find function component' - ag-grid

I am trying to get the ag-grid master-detail view to work with a custom detail view. The default detail view renders a grid, which is not what we need; however, I am running into problems when I try to supply a custom component for ag grid to render as per here. I have tried a couple options after reading through the docs in different places, but each of them seems to run into one issue or another, and wanted to ask if there is some ag-grid voodoo that I might be overlooking that should make this go smoothly/painlessly?
I have tried:
Option 1: using the 'detailCellRenderer' AgGrid option to directly inject a component. Like here
Option 2: using the 'components' option and setting the 'detailCellRenderer' key inside of that to a custom component.
Option 3: using both the 'components' and 'detailCellRenderer' options by setting the key value pair such that components['detailCellRenderer'] = detailCellRenderer, and passing in a string for 'detailCellRenderer' option to AgGrid, to register the component by name. Both option 2 and 3 were inspired by the docs here
Option4: trying to override the default cell renderer as described here, by setting the 'agGridDetailCellRenderer' in the 'components' option to my custom component.
I am just trying to get a basic custom cell rendering, and so I have taken the custom component from the docs here.
NOTE: I am using version 24.0.0 of ag-grid-react behind the scenes; the AGGrid component is just a simple wrapper that loads all of the enterprise version modules and some default props and such, and forwards everything to AGGridReact
Code for the various options below:
Option 1
const DetailCellRenderer = () => (
<h1 style={{ padding: '20px' }}>My Custom Detail</h1>
);
const detailCellRenderer = useMemo<any>(() => {
return DetailCellRenderer;
}, []);
const components = {
detailCellRenderer: detailCellRenderer
}
return (
<>
<Box>
<TableHeading />
<ErrorOverlay
errorMessage={ADVANCED_COMMISSION_ERROR}
isErrorMessage={!!advancedCommissionTransactionsError}
>
<Box width="100%" maxWidth="1050px" marginTop={1} className={classes.agTable}>
<Card>
<AgGrid
{...TRANSACTION_GRID_DEFAULT_PROPS}
columnDefs={ADVANCED_COMMISSION_GRID_COLS}
rowData={gridTransactionData}
onGridReady={onGridReady}
masterDetail={true}
//components={components}
detailCellRenderer={detailCellRenderer}
detailCellRendererParams={{}}
/>
Option 2
const DetailCellRenderer = () => (
<h1 style={{ padding: '20px' }}>My Custom Detail</h1>
);
const detailCellRenderer = useMemo<any>(() => {
return DetailCellRenderer;
}, []);
const components = {
detailCellRenderer: detailCellRenderer
}
return (
<>
<Box>
<ErrorOverlay
errorMessage={ADVANCED_COMMISSION_ERROR}
isErrorMessage={!!advancedCommissionTransactionsError}
>
<Box width="100%" maxWidth="1050px" marginTop={1} className={classes.agTable}>
<Card>
<AgGrid
{...TRANSACTION_GRID_DEFAULT_PROPS}
columnDefs={ADVANCED_COMMISSION_GRID_COLS}
rowData={gridTransactionData}
onGridReady={onGridReady}
masterDetail={true}
components={components}
// detailCellRenderer={detailCellRenderer}
detailCellRendererParams={{}}
/>
Option 3
const DetailCellRenderer = () => (
<h1 style={{ padding: '20px' }}>My Custom Detail</h1>
);
const detailCellRenderer = useMemo<any>(() => {
return DetailCellRenderer;
}, []);
const components = {
'detailCellRenderer': detailCellRenderer
}
return (
<>
<Box>
<TableHeading />
<ErrorOverlay
errorMessage={ADVANCED_COMMISSION_ERROR}
isErrorMessage={!!advancedCommissionTransactionsError}
>
<Box width="100%" maxWidth="1050px" marginTop={1} className={classes.agTable}>
<Card>
<AgGrid
{...TRANSACTION_GRID_DEFAULT_PROPS}
columnDefs={ADVANCED_COMMISSION_GRID_COLS}
rowData={gridTransactionData}
onGridReady={onGridReady}
masterDetail={true}
components={components}
detailCellRenderer='detailCellRenderer'
detailCellRendererParams={{}}
/>
Option 4
const DetailCellRenderer = () => (
<h1 style={{ padding: '20px' }}>My Custom Detail</h1>
);
const detailCellRenderer = useMemo<any>(() => {
return DetailCellRenderer;
}, []);
const components = {
'detailCellRenderer': detailCellRenderer
}
return (
<>
<Box>
<TableHeading />
<ErrorOverlay
errorMessage={ADVANCED_COMMISSION_ERROR}
isErrorMessage={!!advancedCommissionTransactionsError}
>
<Box width="100%" maxWidth="1050px" marginTop={1} className={classes.agTable}>
<Card>
<AgGrid
{...TRANSACTION_GRID_DEFAULT_PROPS}
columnDefs={ADVANCED_COMMISSION_GRID_COLS}
rowData={gridTransactionData}
onGridReady={onGridReady}
masterDetail={true}
components={components}
detailCellRenderer='detailCellRenderer'
detailCellRendererParams={{}}
/>
The corresponding errors for each attempt (option 1,2,3,4) are as follows:
Option 1
Option 2
Option 3
Option 4

It turns out, as per the documentation for version 24.0.0, that in order to use custom components one first needs to register it using the 'frameworkComponents' prop, like so:
const DetailCellRenderer = () => (
<h1 style={{ padding: '20px' }}>My Custom Detail</h1>
);
const detailCellRenderer = useMemo<any>(() => {
return DetailCellRenderer;
}, []);
<AgGrid
{...TRANSACTION_GRID_DEFAULT_PROPS}
columnDefs={ADVANCED_COMMISSION_GRID_COLS}
rowData={gridTransactionData}
onGridReady={onGridReady}
masterDetail={true}
detailCellRenderer={'advancedCommissionsDetailCellRenderer'}
frameworkComponents={{ advancedCommissionsDetailCellRenderer: detailCellRenderer }}
/>
This is not required in version 27, which is what the docs default to, so the example provided there is not a complete one.

Related

storybook mui 5 - listbox not found

I write test in storybook for my Select component. Which is basically styled Select from MUI ver. 5. I have trouble to access role="listbox" since it is outside root.
<div id="root"> Storybook render Select here</div>
<div role="presentation">
Here are my Select options
<ul role="listbox" ></u> - cannot acces this role after click. Unable to find an accessible element with the role "listbox"
<div>
This is my story.
const Template: ComponentStory<typeof StyledSelect> = (args) => <StyledSelect {...args} />;
export const ChangeOption = Template.bind({});
ChangeOption.args = {
labelId: 'dafault-select-label',
id: 'default-select',
label: 'select-under-test',
sx: { width: 240 },
value: 1,
children: [
<StyledMenuListItem key={'none1'} value={-1}>
None
</StyledMenuListItem>,
<StyledMenuListItem key={1} value={1}>
Option 1
</StyledMenuListItem>,
<StyledMenuListItem key={2} value={2}>
Option 2
</StyledMenuListItem>,
<Divider key="div3" />,
<StyledMenuListItem key={3} value={3}>
Option 3
</StyledMenuListItem>,
],
};
ChangeOption.play = async ({ canvasElement, globals }) => {
// Arrange
const canvas = within(canvasElement);
const selectElement = canvas.getByLabelText(/select-under-test/i);
await expect(selectElement).toHaveTextContent(/option 1/i);
// Act
await userEvent.click(selectElement);
const listbox = await canvas.getByRole('listbox');
// Unable to find an accessible element with the role "listbox"
};

material text field label not copyable?

I am using MUI's Text Field component and found there's literally no way to copy the label contents. Is there a way to copy the label somehow?
See the demo here: https://codesandbox.io/s/4ou0l7?file=/demo.tsx
Thanks
It is because material UI is disabling the label selection using CSS.
You can enable it back in a few ways. You can enable it for a certain field or across all of them using the material UI theme override ability.
In order to enable label selection only to one field, you have pass an additional prop to your TextField: InputLabelProps={{ sx: { userSelect: "text" } }}
And here I have provided you with the second way to do that for all the text fields:
import * as React from "react";
import Box from "#mui/material/Box";
import TextField from "#mui/material/TextField";
import { createTheme, ThemeProvider } from "#mui/material/styles";
const theme = createTheme({
components: {
MuiInputLabel: {
styleOverrides: {
root: {
userSelect: "text"
}
}
}
}
});
const StateTextFields = () => {
const [name, setName] = React.useState("Cat in the Hat");
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value);
};
return (
<Box
component="form"
sx={{
"& > :not(style)": { m: 1, width: "25ch" }
}}
noValidate
autoComplete="off"
>
<TextField
id="outlined-name"
label="Name"
value={name}
onChange={handleChange}
/>
<TextField
id="outlined-uncontrolled"
label="Uncontrolled"
defaultValue="foo"
/>
</Box>
);
};
export default () => (
<ThemeProvider theme={theme}>
<StateTextFields />
</ThemeProvider>
);
Of course, you can extract this ThemeProvider into a separate file and wrap with it the whole project, not only this file. It is combined just for the example.
I found a way that this can be done using the helperText and my solution is more of a hack. I am using the helperText and positioning it where the label was used to be, giving it a background and bringing it to front using z-index.
Also you can either choose to use the label or replace it with the placeholder depending if you are happy with the animation.
Here is a codesandbox based on your code in case you need it.
<TextField
id="outlined-name"
label="Name"
// placeholder="Name"
value={name}
onChange={handleChange}
helperText="Name"
sx={{
"& .MuiFormHelperText-root": {
top: "-11px",
position: "absolute",
zIndex: "1",
background: "white",
padding: "0 4px",
}
}}
/>

Adhoc styling in Material-UI 5

Trying to upgrade from MUI 4 to 5. In MUI 4, I used to be able to introduce adhoc classes using makeStyles:
const useStyles = makeStyles((theme: Theme) => ({
root: {
cursor: 'pointer',
},
}));
export const FakeLink = ({ children }) => {
const classes = useStyles();
return (
<span className={classes.root}>
{children}
</span>
);
};
Now trying to put the same cursor: 'pointer' on an ImageListItem. How do I do this?
<ImageListItem>
<img ... />
</ImageListItem>
I thought I could do it using sx, but it doesn't implement the cursor property. What am I missing?
The cursor property should be supposed by the sx and styled API. Maybe you are facing a TypeScript issue?

Material UI Autocomplete + Infinite Scrolling together?

Problem : Getting double Scrollbars - Removing Paper Scrollbar makes the autocomplete content not scrollable hence showing ONLY the contents in the visible height of the dropdown.
If I hide the other scroll then the Infinite Scroll API does not get invoked. How can I get it working :
Description -
I am trying to create a Infinite Scroll with Material UI Autocomplete for which I am using react-infinite-scroll-component attached link for reference
The way I implemented is :
As we need to attach the Infinite Scroll to the Popper that renders the list items; hence I have written my custom PAPER Component (as per documentation it is responsible for rendering items in the dropdown )
PaperComponent={myCustomComponent}
My InfiniteScrollAutoComplete definition is attached below :
<Autocomplete
options={list.data && list.data !== null ? list.data : []}
getOptionLabel={(option) => option.name}
PaperComponent={(param) => (
<InfiniteScroll
height={200}
dataLength={list.total}
next={this.handleFetchNext.bind(this)}
hasMore={list.data.length < list.total ? true : false}
loader={
<p style={{ textAlign: "center", backgroundColor: "#f9dc01" }}>
<b>Loading...</b>
</p>
}
endMessage={
<p style={{ textAlign: "center", backgroundColor: "#f9dc01" }}>
<b>Yay! You have seen it all</b>
</p>
}
>
<Paper {...param} />
</InfiniteScroll>
)}
renderInput={(params) => (
<TextField {...params} label="" variant="outlined" />
)}
/>
const observer = useRef();
const lastOptionElementRef = useCallback((node) => {
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(async (entries) => {
if (entries[0].isIntersecting && props.hasMore) {
setPageNumber((pageNumber) => pageNumber + 1);
}
});
if (node) observer.current.observe(node);
}, [props.loader]);
you can add this lastOptionElementRef to the last element of the options using the render option prop. This will trigger an function whenever the last option is visible in the viewport. Also, it avoids the scrolling issue

Styling react-select v2 with material-ui - Replace Input component

I'm having an issue with replacing the Input component for react-select v2 with the Input component from Material UI.
I've made an attempt so far in the codesandbox below, but unable to invoke the filtering upon typing into the Input?
https://codesandbox.io/s/jjjwoj3yz9
Also, any feedback on the Option replacement implementation would be appreciated. Am I going about it the right way with grabbing the text of the clicked option and search for the Option object from my options list to pass to the selectOption function?
Much appreciated,
Eric
V1
refer the documentation from here : https://material-ui.com/demos/autocomplete/
it provides clear documentation about how to use react-select with material-ui
here is a working example for your question: https://codesandbox.io/s/p9jpl9l827
as you can see material-ui Input component can take react-select as inputComponent.
V2
It's almost same as the previous approach :
implement the Input component:
<div className={classes.root}>
<Input
fullWidth
inputComponent={SelectWrapped}
value={this.state.value}
onChange={this.handleChange}
placeholder="Search your color"
id="react-select-single"
inputProps={{
options: colourOptions
}}
/>
</div>
and then SelectWrapped component implementation should be:
function SelectWrapped(props) {
const { classes, ...other } = props;
return (
<Select
components={{
Option: Option,
DropdownIndicator: ArrowDropDownIcon
}}
styles={customStyles}
isClearable={true}
{...other}
/>
);
}
and I overrides the component Option and DropdownIndicator to make it more material and added customStyles also:
const customStyles = {
control: () => ({
display: "flex",
alignItems: "center",
border: 0,
height: "auto",
background: "transparent",
"&:hover": {
boxShadow: "none"
}
}),
menu: () => ({
backgroundColor: "white",
boxShadow: "1px 2px 6px #888888", // should be changed as material-ui
position: "absolute",
left: 0,
top: `calc(100% + 1px)`,
width: "100%",
zIndex: 2,
maxHeight: ITEM_HEIGHT * 4.5
}),
menuList: () => ({
maxHeight: ITEM_HEIGHT * 4.5,
overflowY: "auto"
})
};
and Option:
class Option extends React.Component {
handleClick = event => {
this.props.selectOption(this.props.data, event);
};
render() {
const { children, isFocused, isSelected, onFocus } = this.props;
console.log(this.props);
return (
<MenuItem
onFocus={onFocus}
selected={isFocused}
onClick={this.handleClick}
component="div"
style={{
fontWeight: isSelected ? 500 : 400
}}
>
{children}
</MenuItem>
);
}
}
please find the example from here: https://codesandbox.io/s/7k82j5j1qx
refer the documentation from react select and you can add more changes if you wish.
hope these will help you.