How to make React-Material-UI Popper draggable? - reactjs

I want to dynamically change Popper position on the screen with react-draggable.
Here's my code:
import PopupState, {bindPopper, bindToggle} from "material-ui-popup-state";
...
return (
<PopupState variant={"popper"} popupId='demo-popper'>
{(popupState) => {
// popupState.anchorEl = undefined <-- THIS WORKS BUT ERROR WITH 'anchorEl' IS RAISED
return (
<div>
<Button variant="contained" color="primary" {...bindToggle(popupState)}>
Toggle Popper
</Button>
<Draggable defaultPosition={position}>
<Popper {...bindPopper(popupState)}
transition
className={classes.popper}
placement='bottom-start'
>
<Paper elevation={5}>
<Typography variant={"h2"} className={classes.typography}>
POPUP BODY
</Typography>
</Paper>
</Popper>
</Draggable>
</div>
)
}}
</PopupState>
)
... and CODE Sandbox
As you can see, when I make popupState.anchorEl = undefined dragging functionality works, but error with anchorEl occurs.
Failed prop type: Material-UI: The anchorEl prop provided to the component is invalid.
It should be an HTML element instance or a referenceObject (https://popper.js.org/docs/v1/#referenceObject).
When anchorEl is set, <Draggable> is not working.
I think that on every component render, caused by dragging, Popup's position is being changed, but it's immediately going back to default, due to set anchorEl
Popper.js docs
Does anyone know how to solve that problem?

You do not need to use Popper.
Just use the Draggable component with the Paper.
https://codesandbox.io/s/react-material-ui-popup-draggable-forked-lmylb

Related

Observed this Error: Material-UI: The `anchorEl` prop provided to the component is invalid

Observed the error when tried to open a popper in grid table. Error Details
Material-UI: The anchorEl prop provided to the component is invalid.
The anchor element should be part of the document layout.
Make sure the element is present in the document or that it's not display none.
below is sample code I am using in grid table:
<>
<MoreVertIcon
ref={anchorRef}
aria-controls={open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
// key={uuidv4()}
onClick={handleToggle}
style={{ color: theme.palette.primary.main }}
/>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
hello world
</Popper>
</>
found a reference but not sure where I am breaking this norm. Any help is appreciated.
Finally got the reason, child component was re-rendering because I added dynamic key on map iteration which were causing props to change, as I used iteration index as key issue resolved.
Another way that this error can occur is when using a react ref. There can be a 'race condition' where the element is created before it is rendered, it can be solved by delaying setting the anchor by a frame:
const MyComponent = () => {
const anchorRef = React.useRef()
const [anchorEl, setAnchorEl] = React.useState()
React.useEffect(() => {
setTimeout(() => setAnchorEl(anchorRef?.current), 1)
}, [anchorRef])
return <div ref={anchorRef}>
{anchorEl && <Popper anchorEl={anchorEl}>hello world</Popper>}
</div>
}
In my case the issue was due to the fact my button IconButton was not receiving the actual ref={anchorRef}. Yep, that silly issue...
Also, in my case, I could do well with a name key other than an index like:
{optionsArray.map(option => (
<MenuItem key={option} onClick={handleClose}>
{option}
</MenuItem>
))}

Material UI Popper over TextField how to keep popper open if the popper options are selected

I am using React Material UI, and I have a Textfield which if I focus on it will deploy a Popper with a simple Menu. If the Textfield loses the focus then the Popper closes itself. The thing is I need to select any option from the menu without close the Popper, but when I do that the Textfield loses the focus. What I need is to keep the Popper on only if I click outside of the Textfield or the Menu.
Everything is on this codesandbox.
I tried this:
const selected = prop => {
console.log(prop);
}
...
<Paper elevation={3} className={classes.paper}>
<MenuList>
<MenuItem onClick={() => selected('first')}>
First Option
</MenuItem>
<MenuItem onClick={() => selected('next')}>
Next Option
</MenuItem>
<MenuItem onClick={() => selected('last')}>
And Last Option
</MenuItem>
</MenuList>
</Paper>
</Popper>
Also tried with ClickAwayListener wrapping both components, the TextField and the Popper:
<ClickAwayListener onClickAway={blur}>
<>
<TextField ... />
<Popper ...>
...
</Popper>
</>
</ClickAwayListener>
Unsuccessfully both times... How can I achieve this?
Although the solution by #Dekel is working well enough.
But in my opinion, it would be better if we would use React.useRef() for focusing on the text field.
Here is the updated solution link:
https://codesandbox.io/s/goofy-frost-bb88l?file=/src/MyApp.js
const textFieldRef = React.useRef();
Inside return ()
<TextField
aria-describedby={id}
onFocus={focus}
onBlur={blur}
placeholder="Focus on me"
inputRef={textFieldRef}
/>
On selecting any menu list item
const selected = event => {
console.log("Selected ", event.target.innerText);
textFieldRef.current.focus();
};
I think it's best to implement this using the Autocomplete, but since the OP requested another solution - here is another option:
Once blur - check the element that caused the blur. If that element is one of the items in the popper - don't blur:
if (e.relatedTarget && e.relatedTarget.classList.contains("MuiListItem-root")) {
return;
}
The full blur function will look like this:
const blur = (e) => {
if (e.relatedTarget && e.relatedTarget.classList.contains("MuiListItem-root")) {
e.target.focus();
return;
}
setAnchorEl(null);
};

material UI CardHeader action drop-down

I am trying to add a FormControl, a Select component and a MenuItem to the action prop of the CardHeader IconButton.
Currently the code looks like this:
Rendering:
<CardHeader
action={
<IconButton
onClick={this.renderFilterRequest()}
>
<Edit />
</IconButton>
}
/>
onClick method:
renderFilterRequest() {
const { selection } = this.state;
return (
<div>
<FormControl>
<Select
value={selection}
onChange={this.handleFilterChange}
>
<MenuItem value='1'>January</MenuItem>
<MenuItem value='2'>February</MenuItem>
</Select>
</FormControl>
</div>
);
}
The error I get is onClick listener to be a function, instead got a value of object type. What is the right way to render the dropdown menu on CardHeader action click?
You are returning some div from from this.renderFilterRequest, And you are also calling the function, so the value of onClick becomes the div. But they were meant to be functions, right?
So it should have been just: onClick={this.renderFilterRequest}.
This function also returned a div but there is no way to attach it to rendering logic in render.
You need to put the MenuItems in your render method and show/hide them depending on the state.
Your onClick listener should be a function that changes the state so that the MenuItems become visible.
Here is a simple demo how this should be done:

React - Material-UI Modal causing an error with the tabindex

I am getting this error when I open a modal on my React app but I can't figure out what it means or how to fix it.
"Warning: Material-UI: the modal content node does not accept focus.
For the benefit of assistive technologies, the tabIndex of the node is being set to "-1"."
<SettingsModal event={this.state.eventDetails} id={this.state.eventDetails.id} delete={this.handleRemoveEvent}/>
returns:
return(
<>
<Paper className={classes.SettingsModal}>
<h1>{this.props.event.name}</h1>
<p>{this.props.event.description}</p>
<p>{this.props.event.id}</p>
<Button onClick={(e) => this.props.delete(this.props.event)}>Delete Event</Button>
</Paper>
</>
);
I've found fix! To remove this error you should wrap your Modal content with DialogContent component like this
import DialogContent from '#material-ui/core/DialogContent';
// ...
render () {
return (
<Modal open={this.state.modalOpened} onClose={() => this.setState({ modalOpened: false, modalContent: null })}>
<DialogContent>
{this.state.modalContent}
</DialogContent>
</Modal>
);
}
All the credit goes to #Idos's comment above since he found the reference to the GitHub Issue. I found that this specific comment was useful.
The wrapping element of the Modal Contents needs to have a prop of
tabIndex: {-1}
In your case looks like you need to do the following:
return(
<Paper tabIndex:{-1} ...>
...
</Paper>
);
i had the same problem. apparently wrapping a div around SettingsModal should fix it.
Following #Wolfman comment, I just used Fragment from React, because it doesn't add any DOM element:
render () {
return (
<Modal open={this.state.modalOpened} onClose={() => this.setState({ modalOpened: false, modalContent: null })}>
<>
{this.state.modalContent}
</>
</Modal>
);
}
Even though, I still don't understand that issue :/

Toast message showing behind the Semantic-UI modal when dimmer is set to {'blurring'}

I am using the toast messages in reachjs together with the semantic-ui. The problem is that the toast message is showing behind the modal when dimmer is set to blurring. Otherwise it is showing on the top of the page as expected.
Do you have the same issues? How this can be corrected?
Thanks for your help!
<Modal
centered
size={'large'}
open={this.props.openVariationGeometry}
onClose={() => this.props.closeVariationGeometryModal()}
closeIcon
dimmer={'blurring'}
>
<Header icon="cube" content={'Change the Gemetry of the Selected Variation.'} />
<Modal.Content>
<VariationGeometryForm />
</Modal.Content>
</Modal>
Example
define a css rule for your toast with filter: none, or a general rule like this
.toast-container {
filter: none !important;
}
It is very old question but I ended here looking for a solution to the same problem so I wrote this down. At first, as this issue says, the only way I found to show the toasts with a modal present is by putting the Toast Container inside the modal. This method has the problem that you could see also the "main" toast blurred under the modal.
Later I found a final solution here. Both the toast container and the modal must be mounted on the same DOM node (I used #App).
First you have to put the toast container in the App component:
const App = () => (
<div id='App'>
<YourComponents />
<ToastContainer
className='dimmer toast-container' // 'dimmer' class is required to avoid blurring by modal
/>
</div>
)
After that you need the following css rule:
.blurring.dimmable > .toast-container {
// this prevents the toast from darkening by the modal
background-color: unset;
}
And finally the modal must be mounted in the same node:
<Modal
centered
size={'large'}
open={this.props.openVariationGeometry}
onClose={() => this.props.closeVariationGeometryModal()}
closeIcon
dimmer={'blurring'}
mountNode={document.getElementById('App')} // modal mounted on #App
>
<Header icon="cube" content={'Change the Gemetry of the Selected Variation.'} />
<Modal.Content>
<VariationGeometryForm />
</Modal.Content>
</Modal>

Resources