Material UI popover fails to open on anchor - reactjs

I am trying to have a popover appear over an icon when hovering. I tried to adapt the code from here https://material-ui.com/components/popover/#mouse-over-interaction. My solution is below. I tried to remove some of the complexities of the project setup but had to include some extra stuff for clarity.
export interface Props {
popoverAnchor?: HTMLElement;
setPopoverAnchor: (anchor?: HTMLElement) => void;
}
render()
const {
popoverAnchor,
setPopoverAnchor
} = this.props;
const open = Boolean(popoverAnchor);
return(
...
<div className={classes.flex}>
<HelpOutlineIcon
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={(event: any) => {
setPopoverAnchor(event.currentTarget)
}}
onMouseLeave={() => setPopoverAnchor()}
/>
<Popover
id="mouse-over-popover"
className={(classes.popover)}
classes={{
paper: classes.paper,
}}
open={open}
anchorEl={popoverAnchor}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
onClose={() => setPopoverAnchor()}
disableRestoreFocus
>
<Typography>I use Popover.</Typography>
</Popover>
</div>
constants.ts
export const SET_POPUP_ANCHOR = 'SET_POPUP_ANCHOR';
export type SET_POPUP_ANCHOR= typeof SET_POPUP_ANCHOR;
store.ts
export interface State {
anchor?: HTMLElement;
}
actions.ts
export interface SetPopupAnchor {
type: constants.SET_POPUP_ANCHOR;
anchor?: HTMLElement;
}
export type Action = SetPopupAnchor;
export function handlePopoverAnchor(anchor?: HTMLElement): SetPopupAnchor {
return {
type: constants.SET_POPUP_ANCHOR,
anchor: anchor
}
}
DemoContainer.ts
export function mapDispatchToProps(dispatch: Dispatch<actions.AnonymizationAction>) {
return {
setPopoverAnchor: (anchor?: HTMLElement) => dispatch(actions.handlePopoverAnchor(anchor))
}
}
This fails to create any popup at all and no error appears on the console. after debugging i saw that the element object successfully makes it all the way to the the handlePopoverAnchor action meaning the state should have been changed.

Related

HOC's getting rendered every time

const withSnackbar = (WrappedComponent: React.FunctionComponent<any>): React.FunctionComponent<any> => {
const LoadedWrappedComponent = (props: any): ReactElement<any> => {
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = useState(false);
const [messageData, setMessageData] = useState<IMessageData>({
message: '',
severityType: 'info',
duration: 2000,
vertical: 'bottom',
horizontal: 'right',
});
const showMessage = ({
message,
severityType,
vertical = 'bottom',
horizontal = 'right',
duration = 2000,
}: IMessageData): void => {
setMessageData({
message,
severityType,
duration: horizontal === 'left' ? 5000 : duration,
vertical,
horizontal: theme.direction === 'ltr' ? 'right' : 'left',
});
setOpen(true);
};
const handleClose = (): void => {
setOpen(false);
};
const { message, severityType, duration, horizontal, vertical } = messageData;
return (
<>
<WrappedComponent {...props} showToastMessage={showMessage} />
<Snackbar
anchorOrigin={{
vertical,
horizontal,
}}
autoHideDuration={duration}
open={open}
onClose={handleClose}
TransitionComponent={Slide}
>
<Alert
classes={{
root: classes.root,
}}
variant='filled'
severity={severityType}
action={
horizontal === 'right' ? (
<IconButton>
<Icon src='CloseOutlined' onClick={handleClose} />
</IconButton>
) : (
<></>
)
}
>
{message}
</Alert>
</Snackbar>
</>
);
};
return LoadedWrappedComponent;
};
export default withSnackbar;
**When I am closing toast message but page getting rerendered where my page components also rendering from json template with help of HOC too.. and also after api calls when I dispatch some success message or any reducer .. also my entire template page rerenders
Tried memo no luck.
Tried Render Props no luck**

Material UI Popover - how to open it without Hooks?

Because of an external library I have to use and I cannot avoid, a re-render of a Component I have is causing this error message:
Error: Rendered more hooks than during the previous render.
I know this is caused because I'm using hooks inside my component. See my component content:
import React, { useState } from 'react';
import Box from '#material-ui/core/Box';
import Popover from '#material-ui/core/Popover';
import ImageCell from 'App/components/DatabaseTable/Utils/ImageCell';
const Image = ({ defaultValueRow, valueRow }) => {
const [anchorEl, setAnchorEl] = useState(null);
const id = `popover-${defaultValueRow[valueRow]}`;
const handlePopoverOpen = (event) => setAnchorEl(event.currentTarget);
const handlePopoverClose = () => setAnchorEl(null);
return (
<>
<Box
aria-haspopup={'true'}
aria-owns={anchorEl ? id : undefined}
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
>
<ImageCell src={valueRow[defaultValueRow]} />
</Box>
<Popover
id={id}
style={{ pointerEvents: 'none' }}
PaperProps={{
style: { padding: '8px' },
}}
open={anchorEl}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
>
<ImageCell
style={{ maxWidth: '125rem', maxHeight: '15rem' }}
src={valueRow[defaultValueRow]}
/>
</Popover>
</>
);
};
export default Image;
As I cannot avoid using this external library, I was wondering:
Is there any way to manage anchorEl to not be using the useState hook?

How to store a component and render conditionally

I'm using material-ui popover component wrapping a formik form. it's obvious every time it is closed, the form re renders and all values get cleaned.
I thought about storing values in parent, but it's more complicated and I'm using dynamic component, then it's not a solution here.
I am looking for a way to get a copy of children and just show it inside popover.
Here is my code:
export default function ButtonPopover({ icon: Icon, children }) {
const [anchorEl, setAnchorEl] = useState(null);
const open = (event) => {
setAnchorEl(event.currentTarget);
};
const close = () => {
setAnchorEl(null);
};
return (
<>
<IconButton onClick={open}>
<Icon />
</IconButton>
<Popover
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={close}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
>
// Here is what I need to be saved.
{children}
</Popover>
</>
);
}
I'm not sure what you're trying to do but this is how you render conditionally:
export default function ButtonPopover({ icon: Icon, children }) {
const [anchorEl, setAnchorEl] = useState(null);
const open = (event) => {
setAnchorEl(event.currentTarget);
};
const close = () => {
setAnchorEl(null);
};
const renderChildren = () => {
if (myCondition == true) {
return (
<>
<Text>render when condition equals true</Text>
</>
)
} else {
return (
<>
<Text>render when condition doesn't equal true</Text>
</>
)
}
}
return (
<>
<IconButton onClick={open}>
<Icon />
</IconButton>
<Popover
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={close}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
>
{renderChildren()}
</Popover>
</>
);
}

React Display Separate Popover Component on Menu Click

I have the below menu:
So, ideally you'd click on those 3 dots which would open up another box next to it, I decided to use a popover(https://material-ui.com/components/popover/).. The problem is that when you click the 3 dots nothing happens. I assume this is because onClick function returns a functional popover component but that doesn't get displayed. I put in debuggers and alerts inside the functional component no hit at all.
This is those 3 dots
<IconButton
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e, props.children)}
>
<More />
</IconButton>
This is moreClick function
moreClick = (e, officeAccount) => {
return (
<AccountFavoritesPopover element={e} officeAccount={officeAccount} />
);
};
This is the whole popover
import React from "react";
import Popover from "#material-ui/core/Popover";
export default function AccountFavoritesPopover(element, officeAccount) {
const anchorEl = element;
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
//onClose={alert}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<div>{officeAccount}</div>
</Popover>
</div>
);
}
Does popover have to be inside the main file? Currently the 3 dots is in the main file and AccountFavoritesPopover is a whole separate file.
I attempted to put "AccountFavoritesPopover" code inside the main file but I cannot utilize useState in the main file because it's a class. Also, I cannot convert it to actual state and use setState because once setState kicks in, the menu will disappear...
Edit:
Below is the Menu
<Creatable
id="Select"
menuIsOpen={this.state.Focused}
components={{
Option: this.Option
}}
/>
Below is the Options inside menu
<div style={{ position: "relative" }}>
<components.Option {...props} />
<div id="MoreBox">
<IconButton
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e, props.children)}
>
<More />
</IconButton>
</div>
</div>
Try this, this should work(Not tested)
Main.js
export default class Main extends Component {
constructor(props) {
this.state = {
selectedIndex: 0,
selectedId: 0,
anchorEl: null
};
}
moreClick = (anchorEl, selectedId, selectedIndex) => {
this.setState({
selectedId,
selectedIndex,
anchorEl,
open: true,
});
}
handleClose = () => {
this.setState({
open: false
});
}
render() {
const menu = [
{
id: 1,
text: '002',
more: 'more 003'
},
{
id: 2,
text: '003',
more: 'more 003'
},
{
id: 3,
text: '004',
more: 'more 003'
}
]
const menuDom = menu.map((m, index) => {
return (
<IconButton
key={m.id}
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e.currentTarget, index, m.id)}>
{m.text}
</IconButton>
)
})
const more = (<More>{menu[this.state.selectedIndex].text}</More>)
return (
<div>
{menuDom}
<AccountFavoritesPopover open={this.state.open} anchorEl={this.state.anchorEl} onClose={this.handleClose}>
{more}
</AccountFavoritesPopover>
</div>
)
}
}
AccountFavoritesPopover.js
export default function AccountFavoritesPopover(open, anchorEl, onClose) {
const id = open ? "simple-popover" : undefined;
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={onClose}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<div>{this.props.children}</div>
</Popover>
</div>
);
}

Error: Problem with node not being found using enzyme. Method text

I am writing a small test for a PopUp component. However, I am getting this error: Method “text” is meant to be run on 1 node. 0 found instead. I am in the learning process, any help would be greatly appreciated. Thanks
This is my component.tests.js
const small = popUp.find('small');
expect(small).toHaveLength(1);
expect(small.text()).toBe(messages['en'] .
['segments.create.timeline.isScheduled']);
expect(warning).toBeDefined();
expect(popover).toBeDefined();
expect(message).toBeDefined();
});
});
And this is my component.js
class SegmentWarningPopup extends React.Component {
state = {
anchorEl: null,
};
handleClick = event => {
this.setState({
anchorEl: event.currentTarget,
});
};
handleClose = () => {
this.setState({
anchorEl: null,
});
};
render() {
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
return (
<div>
<Warning size="small" className="duration-has-changed-icon" onClick={this.handleClick}/>
<Popover
id="durationHasChanged"
open={open}
anchorEl={anchorEl}
onClose={this.handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
PaperProps={{
className: 'warning-popover-paper'
}}
>
<FormattedMessage id={"segments.create.timeline.isScheduled"} description="text"/>
</Popover>
</div>
);
}
}
SegmentWarningPopup.propTypes = {
targetEventObj: PropTypes.object
};
export default SegmentWarningPopup;
You just need to .find the <Warning> component itself:
popUp.find(Warning)

Resources