Material UI Autocomplete + Infinite Scrolling together? - autocomplete

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

Related

Combine Treeview Mui 5 and Datagrid to display but error as follows

I have 2 components:
1 is the treeview created as follows:
const renderTree = (nodes) => (
<TreeItem key={nodes.id} nodeId={nodes.id ? nodes.id : "defaultNodeId"} label={nodes.name ? nodes.name : "defaultNodeName"}
>
{Array.isArray(nodes.children)
? nodes.children.map((node) => renderTree(node))
: null}
</TreeItem>
);
<TreeView
aria-label="gmail"
defaultCollapseIcon={<ArrowDropDownIcon />}
defaultExpandIcon={<ArrowRightIcon />}
defaultEndIcon={<div style={{ width: 18 }} />}
defaultExpanded={['72']}
sx={{ height: 'auto', flexGrow: 1, maxWidth: 250, overflowY: 'auto' }}
onNodeSelect={handleSelectedItems}
>
{renderTree(data)}
</TreeView>
2 is the datagrid created as follows:
<DataGrid
rows={gridrows}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
{/*//checkboxSelection*/}
{/*//disableRowSelectionOnClick*/}
/>
When I click on the node on the TreeView, the below function will be executed:
const handleSelectedItems = (event, nodeId) => {
console.log('ON fetchGridData', nodeId);
event.preventDefault();
event.stopPropagation();
fetchGridData(nodeId);
event.preventDefault();
event.stopPropagation();
}
I have selected the data, but Treenode does not expand. Does anyone know what the error is? Please help me
I was able to select data and fill in datagrid, but every time I fill the treeview doesn't open node expand

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

Unable to display menu on right click react-big-calendar

I am trying to display menu on right click with react-big-calendar and material ui,
the issue that menu isnt display correctly on html its going on top right corner,
My code is:
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
return (
<>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={() => redirectToEvent(selectedEvent)}>
<ImportContactsTwoToneIcon
color="primary"
style={{ marginLeft: "15px" }}
/>{" "}
פתח אירוע
</MenuItem>
</Menu>
<Calendar
localizer={localizer}
events={events}
step={60}
views={["month", "day"]}
onSelectEvent={(event, e) => {
redirectToEvent(event);
}}
components={
{
eventWrapper: ({ event, children }) => (
<div
onContextMenu={
e => {
setSelectedEvent(event);
//think this is the issue
setAnchorEl(e);
e.preventDefault();
}
}
>
{children}
</div>
)
}
}
Material-UI has an example of providing a Context Menu, and it doesn't seem to use an anchorEl prop, or take the bare 'event' target object, placing a different object in state.
const handleContextMenu = (event) => {
event.preventDefault();
setContextMenu(
contextMenu === null
? {
mouseX: event.clientX - 2,
mouseY: event.clientY - 4,
}
: // repeated contextmenu when it is already open closes it with Chrome 84 on Ubuntu
// Other native context menus might behave different.
// With this behavior we prevent contextmenu from the backdrop to re-locale existing context menus.
null,
);
};
const handleClose = () => {
setContextMenu(null);
};
From that part of the example, it would seem you need to update your onContextMenu accordingly. Since you're setting multiple state values for your menu, both for it's positioning and the referenced selectedEvent, you may want to use a reducer for state instead.
Then, on the <Menu> itself, it also mutates that state.
<Menu
open={contextMenu !== null}
onClose={handleClose}
anchorReference="anchorPosition"
anchorPosition={
contextMenu !== null
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
: undefined
}
>
// menu items
</Menu>
And, since you've placed the <Menu> inside of your <Calendar>'s container object, what sort of styling is on your container and could that effect it's layout as well? (I don't know if Material-UI automatically portals it's menu in this scenario or not)

Material UI Tabs no selected tab

I'm trying to use Material UI Tabs for navigation. However, there are routes in my application that match none of the tabs. When I pass a value to the Tabs component that does not match any of the child tab values, I get a warning about an invalid value.
I created a hidden tab will a value of null as a work-around.
Is it possible to disable this warning about an invalid tab value?
Can tabs in Material UI have no selection?
Thanks
The value of the currently selected Tab. If you don't want any selected Tab, you can set this property to false.
From: https://material-ui.com/api/tabs/
What I ended up doing is creating a switch statement with valid tab values, and if windows.location.pathname doesn't match any of them have the default return false.
Example Routes:
class Routes extends Component {
render() {
return (
<Switch>
<Route path={'/test2'} component={Test2} />
<Route path={'/test3'} component={Test3} />
<Route exact path={'/'} component={Test} />
</Switch>
);
}
}
Example NavBar:
checkPathnameValue() {
const { pathname } = window.location;
switch (pathname) {
case '/test2':
case '/test3':
break;
default:
return false;
}
return pathname;
}
render() {
const { classes } = this.props;
const tabValue = this.checkPathnameValue();
return (
<div className={classes.root}>
<AppBar position={'static'}>
<Toolbar>
<Tabs value={tabValue}>
<Tab
label={'Test 2'}
value={'/test2'}
to={'/test2'}
component={Link}
/>
<Tab
label={'Test 3'}
value={'/test3'}
to={'/test3'}
component={Link}
/>
</Tabs>
</Toolbar>
</AppBar>
</div>
);
}
Seems like setting the value property of Tabs to false will not show any warning and will deselect all the tabs correctly.
Philip's solution works perfectly, here I am just removing the need for the switch case.
In my case, I only had one tab (Login) where I wanted no tab to be selected since it is a button rather than a tab.
Here's what I did to solve this:
<Tabs value={this.state.content !== "login" ? this.state.content : false} onChange={(event, newValue) => { this.setState({content: newValue}) }}>
<Tab value="home" label="Home" wrapped />
<Tab value="tab1" label="Tab 1" />
<Tab value="tab2" label="Tab 2" />
</Tabs>
on another part of my AppBar I had a Login button:
<Button onClick={(event, newValue) => { this.setState({content: "login"}) }}>Login</Button >
Similarly to Philips's answer, the key is in {this.state.content !== "login" ? this.state.content : false} which prevents Tabs from being rendered with "login" value. Instead, it is given the value "false" which is allowed and does not invoke the warning.
I also experienced this issue a while back and follow the same pattern.
E.g.,
return <Tabbar value={value ?? false} onChange={(event: React.ChangeEvent<{}>, value: any) => onChange(value)}>{tabs}</Tabbar>
Toggle Effect
To get a toggle effect the listener will need to be placed on the individual <Tab onClick/> events as <Tabs onChange> will not trigger when clicking the same button multiple times.
const Container = ()=>{
const [currentTab,setCurrentTab] = React.useState<string|false>('a')
const handleChange = (val: string) =>
setCurrentTab(val === currentTab ? false : val)
return <Tabs value={currentTab}>
<Tab value='a' label='a' onClick={()=>handleChange('a')}/>
<Tab value='b' label='b' onClick={()=>handleChange('b')}/>
</Tabs>
}

React: how to use child FormItem components without getting Warning: validateDOMNesting: <form> cannot appear as a descendant of <form>

Given the parent component, I am using a child component DynamicFieldSet that is a grouping of FormItems. But I am receiving the error:
Warning: validateDOMNesting(...): <form> cannot appear as a descendant of <form>. See CreateTopic > Form > form > ... > DynamicFieldSet > Form > form.
I have tried to remove the <Form> </Form> tags in my child component, but then it is a compile error.
Is there a way I can disable rendering of the child Form view?
Parent component
class CreateTopic extends React.Component {
render() {
return (
<div className="create-topic-container">
<h3>Create an event</h3>
<Form onSubmit={this.handleSubmit}>
<FormItem>...</FormItem>
<FormItem>...</FormItem>
<FormItem>...</FormItem>
<FormItem
{...formItemLayout}
label="Results"
style={{ marginBottom: SPACING_FORM_ITEM }}
>
{getFieldDecorator('results', {
rules: [
{
required: true,
message: 'Results cannot be empty.',
},
],
})(<DynamicFieldSet
form={this.props.form}
/>)}
</FormItem>
</Form>
</div>
);
}
}
DynamicFieldSet - Child component
export class DynamicFieldSet extends React.Component {
render() {
getFieldDecorator('keys', { initialValue: ['0', '1'] });
const keys = getFieldValue('keys');
const formItems = keys.map((k, index) => {
return (
<FormItem
{...formItemLayoutWithOutLabel}
required={false}
key={k}
>
{getFieldDecorator(`results[${k}]`, {
validateTrigger: ['onChange', 'onBlur'],
rules: [
{
required: true,
whitespace: true,
message: 'Result name cannot be empty.',
},
{
validator: this.validateLength,
},
],
})(<Input placeholder={`Result #${index + 1}`} style={{ width: '80%', marginRight: 8 }} />)}
{keys.length > 2 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
disabled={keys.length === 1}
onClick={() => this.remove(k)}
/>
) : null}
</FormItem>
);
});
return (
<Form>
{formItems}
<FormItem {...formItemLayoutWithOutLabel}>
{keys.length < 10 ? (
<Button type="dashed" onClick={this.add} style={{ width: '80%' }}>
<Icon type="plus" />Add Result
</Button>
) : null}
</FormItem>
</Form>
);
}
}
I faced this issue when using ant design table and turns out its not ant design which throws the warning. It's the web standards description
"Every form must be enclosed within a FORM element. There can be several forms in a single document, but the FORM element can't be nested."
So, there should not be a form tag inside a form tag.
To solve the issue (in our case), remove the Form tag inside the DynamicFieldSet "return" and replace with a div tag
Hope it helps :)
You can portal a form like this:
import Portal from '#material-ui/core/Portal';
const FooComponent = (props) => {
const portalRef = useRef(null);
return <>
<form>
First form
<div ref={portalRef} />
</form>
<Portal container={portalRef.current}>
<form>Another form here</form>
</Portal>
</>;
}
In the example above I use the react material-ui Portal component. But you can try to implement it with React Portals as well
If you're using MUI, the Box component contains an attribute that identifies them as any native HTML container; form is one of them. E.g:
<Box
xs={6}
sx={{
"& > :not(style)": { m: 1, width: "25ch" },
}}
component="form"
noValidate
autoComplete="off"
>
In such case, we just need to delete that attribute, it will default to a DIV. The form will continue to work as expected, and the error will disappear off the console.
In my case this is occur bcoz of i declared <form> inside another <form/> tag.