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**
Related
I am following the react material ui doc here: https://mui.com/components/popover/, and I'm attempting to factor all the popover view and state logic into a higher order component. The one from the doc is here:
function BasicPopoverFromDoc(props) {
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
return (
<div>
<Typography
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
style={{color:'white'}}
>
Hover with a Popover.
</Typography>
<Popover
id="mouse-over-popover"
sx={{
pointerEvents: 'none',
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography sx={{ p: 1 }}>I use Popover.</Typography>
</Popover>
</div>
);
}
I lifted all the state logic into a hoc as follows, taking <Child/> as a param:
function WithPopOver(props){
const [anchorEl, setAnchorEl] = React.useState(null);
const handlePopoverOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const { Child } = props;
console.log('with Popover: ', open)
return (
<div>
<Child
{...props}
aria-owns={open ? 'mouse-over-popover' : undefined}
aria-haspopup="true"
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
/>
<Popover
id="mouse-over-popover"
sx={{
pointerEvents: 'none',
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
onClose={handlePopoverClose}
disableRestoreFocus
>
<Typography sx={{ p: 1 }}>I use Popover.</Typography>
</Popover>
</div>
);
}
but when i try to use it this way:
function BasicPopover(props){
return (
<WithPopOver
{...props}
Child={() => <Typography style={{color:'white'}}> wrapped in hoc </Typography>}
/>
)
}
It seems like the popover does not display. It refuses to display even when I change open to true by force. What am i missing here?
You haven't passed Child props to it's children, so onMouseEnter and onMouseLeave never fires, try on this:
<WithPopOver
{...props}
Child={(props) => <Typography style={{color:'white'}} {...props}> wrapped in hoc </Typography>}
/>
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>
</>
);
}
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.
I use Snack bar from Materia-UI page (first example - Customized SnackBars)
const variantIcon = {
success: CheckCircleIcon,
warning: WarningIcon,
error: ErrorIcon,
info: InfoIcon,
};
const styles1 = theme => ({
success: {
backgroundColor: green[600],
},
error: {
backgroundColor: theme.palette.error.dark,
},
info: {
backgroundColor: theme.palette.primary.dark,
},
warning: {
backgroundColor: amber[700],
},
icon: {
fontSize: 20,
},
iconVariant: {
opacity: 0.9,
marginRight: theme.spacing.unit,
},
message: {
display: 'flex',
alignItems: 'center',
},
});
function MySnackbarContent(props) {
const { classes, className, message, onClose, variant, ...other } = props;
const Icon = variantIcon[variant];
return (
<SnackbarContent
className={classNames(classes[variant], className)}
aria-describedby="client-snackbar"
message={
<span id="client-snackbar" className={classes.message}>
<Icon className={classNames(classes.icon, classes.iconVariant)} />
{message}
</span>
}
action={[
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={onClose}
>
<CloseIcon className={classes.icon} />
</IconButton>,
]}
{...other}
/>
);
}
MySnackbarContent.propTypes = {
classes: PropTypes.object.isRequired,
className: PropTypes.string,
message: PropTypes.node,
onClose: PropTypes.func,
variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired,
};
const MySnackbarContentWrapper = withStyles(styles1)(MySnackbarContent);
const styles2 = theme => ({
margin: {
margin: theme.spacing.unit,
},
});
class CustomizedSnackbar extends React.Component {
state = {
open: false,
};
handleClick = () => {
this.setState({ open: true });
};
handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ open: false });
};
render() {
return (
<div>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={this.state.open}
autoHideDuration={2000}
onClose={this.handleClose}
>
<MySnackbarContentWrapper
onClose={this.handleClose}
variant="error"
message="This is an error message!"
/>
</Snackbar>
</div>
);
}
}
export default withStyles(styles2)(CustomizedSnackbar);
In the example the snack bar is shown when click on button "OPEN SUCCESS SNACKBAR"
I would like to show the error snack bar when Mutation from Apollo on my Form gives an error.
render(){
return(
<div>
<Mutation
mutation={this.mutationQuery}
onError={() =>
//here show Snack Bar
}
onCompleted={data => { console.log(data); }}
>
{mutation => (
//here is the form
)}
)}
The problem is I dont know how to trigger to show the SnackBar in the on Error function. How to change state of Snack Bar? I was trying the solution from here, but I receive an error that
openSnackbarFn is not a function
Thanks in advance.
Fundamentally, you want your Snackbar to be a sibling of your Mutation, and let their common parent (i.e. your component) handle the Snackbar open/closed state.
Class-style component
class FormWithMutationAndSnackbar extends React.Component {
state = {
open: false
}
handleOpen = () => this.setState({ open: true })
handleClose = () => this.setState({ open: false })
render() {
const { open } = this.state
return(
<React.Fragment>
<Mutation
mutation={this.mutationQuery}
onError={(err) => {
// use err to set Snackbar contents for example
this.handleOpen()
}
onCompleted={data => { console.log(data); }}
>
{mutation => (
//here is the form
)}
</Mutation>
<Snackbar
open={open}
onClose={this.handleClose}
// other Snackbar props
>
// Snackbar contents
</Snackbar>
</React.Fragment>
)
}
}
Functional component with Hooks
const FormWithMutationAndSnackbar = () => {
const [open, setOpen] = useState(false)
return(
<React.Fragment>
<Mutation
mutation={this.mutationQuery}
onError={(err) => {
// use err to set Snackbar contents for example
setOpen(true)
}
onCompleted={data => { console.log(data); }}
>
{mutation => (
//here is the form
)}
</Mutation>
<Snackbar
open={open}
onClose={() => setOpen(false)}
// other Snackbar props
>
// Snackbar contents
</Snackbar>
</React.Fragment>
)
}
Hi I'm having troubles testing my component function handlers.
I have this component:
class MUIToolbar extends React.Component {
state = {
title: this.props.title,
anchorEl: null,
value: 0,
open: false
};
handleMenu = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
handleChange = (event, value) => {
this.setState({ value });
};
homeRedirect = () => {
this.props.history.push('/groups');
}
render() {
const { classes } = this.props;
const { anchorEl, value, title } = this.state;
const open = Boolean(anchorEl);
return (
<div className={classes.root}>
<AppBar position="fixed" className={classes.root}>
<Toolbar>
<Typography variant="h6" color="inherit" className={classes.grow} onClick={this.homeRedirect}>
{title}
</Typography>
<Tabs
value={value}
onChange={this.handleChange}
classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }}>
<Tab
classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
label="Groups"
component={Link} to="/groups"/>
</Tabs>
<div>
<IconButton
aria-owns={open ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={this.handleMenu}
color="inherit">
<AccountCircle />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={open}
onClose={this.handleClose}>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>Log out</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
<Switch>
<Route path="/groups" component={Groups} />
</Switch>
</div>
);
}
}
const enhance = compose(
defaultProps({
title: 'Daily Track',
}),
withStyles(styles),
withRouter,
);
export default enhance(MUIToolbar);
So, I want to test handleMenu, handleClose, handleChange and homeRedirect and I'm doing it like this way:
describe('Testing Toolbar component', () => {
let mount;
let shallow;
let mountWrapper;
let shallowWrapper;
let props = {};
beforeEach(() => {
props.onClick = jest.fn();
mount = createMount();
shallow = createShallow();
mountWrapper = mount(<Router><MUIToolbar /></Router>);
shallowWrapper = shallow(<MUIToolbar />);
});
it('should simulate user IconButton click.', () => {
const instance = mountWrapper.instance();
const userIconButton = shallow(<IconButton />);
userIconButton.simulate('click');
jest.spyOn(instance, 'handleMenu');
expect(instance.handleMenu).toHaveBeenCalled();
});
});
And I got this error
Cannot spy the handleMenu property because it is not a function; undefined given instead
I tried many ways to do that but I couldn't.
Thanks for the help