I can't give 100% width on the snackbar and I also have a snackbarClose method but I can't implement it on the snackbar. I also want a button 'X' which button will perform the snackbarClose method.
CodeSandbox : https://codesandbox.io/s/xenodochial-kapitsa-f5yd7?file=/src/Demo.js:693-706
import React, { Component } from "react";
import { Container, Grid, Button, Snackbar } from "#material-ui/core";
import MuiAlert from "#material-ui/lab/Alert";
import { withStyles } from "#material-ui/core/styles";
const styles = (theme) => ({});
function Alert(props) {
return <MuiAlert elevation={6} variant="filled" {...props} />;
}
class Demo extends Component {
constructor() {
super();
this.state = {
snackbaropen: false,
snackbarmsg: "",
severity: ""
};
this.onClick = this.onClick.bind(this);
}
onClick = (event) => {
this.setState({
snackbaropen: true,
snackbarmsg: "Data Saved",
severity: "success"
});
};
snackbarClose = (event) => {
this.setState({ snackbaropen: false });
};
render() {
const { classes } = this.props;
return (
<React.Fragment>
<Container>
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={6} lg={6} xl={6}>
<Button
variant="contained"
color="primary"
onClick={this.onClick}
>
Submit
</Button>
<Snackbar
open={this.state.snackbaropen}
autoHideDuration={3000}
onClose={this.snackbarClose}
>
<Alert severity={this.state.severity}>
{this.state.snackbarmsg}
</Alert>
</Snackbar>
</Grid>
</Grid>
</Container>
</React.Fragment>
);
}
}
export default withStyles(styles, { withTheme: true })(Demo);
If you want 100% width on snackbar, you need to specify width for Alert and Snackbar component and for close button you need to specify onClose function on Alert component.
<Snackbar
open={this.state.snackbaropen}
autoHideDuration={3000}
onClose={this.snackbarClose}
style={{ width: "100%" }} // specify width 100%
>
<Alert
onClose={this.snackbarClose} // specify onClose method
severity={this.state.severity}
style={{ width: "100%" }} // specify width 100%
>
{this.state.snackbarmsg}
</Alert>
</Snackbar>
For snackback closing on outside click, you need to change close function like below:-
snackbarClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ snackbaropen: false });
};
Demo: https://codesandbox.io/s/heuristic-khayyam-xo464
This is so complicated to solve problem.
What React version used?
If you used React(over 16.8), I recommend solution I used.
Use React.createContext, React.useContext
Use React.useReducer
Make custom Hook like useSnackBar
Mount SnackBarProvider on the App
import React from 'react';
import ToastContext, { ToastProps } from './ToastContext';
const useToast = () => {
const [, dispatch] = React.useContext(ToastContext);
const message = (toast: ToastProps) => {
dispatch({
type: 'ADD_TOAST',
payload: toast,
});
};
return message;
};
export { useToast };
export default useToast;
const ToastProvider: React.FC<ToastProviderProps> = ({
children,
placement = 'top-right',
timeout = 5000,
}) => {
const [toasts, dispatch] = React.useReducer(toastReducer, []);
return (
<ToastContext.Provider value={[toasts, dispatch]}>
<ToastProviderWrapper>
<ToastProviderContainer className={classnames(placement)}>
{toasts.map((toast, index) => (
<ToastCard {...toast} key={index} timeout={timeout} />
))}
</ToastProviderContainer>
</ToastProviderWrapper>
{children}
</ToastContext.Provider>
);
};
const Index = () => (
<ToastProvider>
<App />
</ToastProvider>
);
const rootElement = document.getElementById('root');
ReactDOM.render(<Index />, rootElement);
You can give data through dispatch(action), reducers
You can Dispatch event all the pages.
These Doms were rendered Root Element like React.Portal. then you can edit global position styles as system.
The SnackBar component delegates to or inherits its style or appearance from its children component, so you can instead adjust the width of the Alert component inside the SnackBar.
<Alert style={{ width: "100%" }} severity={this.state.severity}>
{this.state.snackbarmsg}
</Alert>
This should also adjust the width of the SnackBar component and make it fullwidth.
Related
Merry Christmas at first to all of you!
[React + TypeScript]
And yes, I'm a newbie in react, I'm more kind of backend geek, but we all need to learn new stuff :)
I am trying to make my snackbar work in all components globally, to not write a new one every time.
I saw one post about this before: How to implement material-ui Snackbar as a global function?
But unfortunatelly I can't make it work.
Is my function even correct?
And if it is, how can I call it from another component now?
I made this function:
SnackbarHOC.tsx
`
import { AlertTitle, IconButton, Snackbar } from '#mui/material';
import Slide from '#mui/material';
import Alert from '#mui/material';
import { useState } from 'react';
import CloseIcon from '#mui/icons-material/Close';
function SnackbarHOC<T>(WrappedComponent: React.ComponentType<T>, Alert: React.ElementType) {
const [open, setOpen] = useState(false);
const [message, setMessage] = useState("I'm a custom snackbar");
const [duration, setDuration] = useState(2000);
const [severity, setSeverity] = useState(
"success"
); /** error | warning | info */
return (props: T) => {
const showMessage = (message: string, severity = "success", duration = 2000) => {
setMessage(message);
setSeverity(severity);
setDuration(duration);
setOpen(true);
};
const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
}
return (
<>
<WrappedComponent {...props} snackbarShowMessage={showMessage} />
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
autoHideDuration={duration}
open={open}
onClose={handleClose}
//TransitionComponent={Slide}
>
<Alert severity="error"
sx={{ width: '100%' }}
action={
<IconButton
aria-label="close"
color="inherit"
size="small"
onClick={handleClose}>
<CloseIcon fontSize="inherit" />
</IconButton>
}>
{message}
</Alert>
</Snackbar>
</>
);
};
};
export default SnackbarHOC;
`
I tried to call it from another component, but I have no idea how to show the actual snackbar now :(.
It's all that is not giving me errors right now:
`
import SnackbarHOC from '../../SnackbarHOC';
`
I found a solution for that but in JS basically, it's the same I guess it's just not typed. I'm implementing within NEXTJS latest
//index.jsx
import Head from 'next/head'
import { useState } from 'react';
import {Button} from '#mui/material'
import Notification from '../components/Notification.component'
//create the jsx file with the Snackbar component implemented
function Home() {
// we need a simple hook for the button
const [open, setOpen] = useState(false);
}
const handleClick = () => {
setOpen(true);
};
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return(
<div >
<Head>
<title>HOME</title>
<meta name="description" content="AWESOME" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Button variant="outlined" onClick={handleClick}>SEND</Button>
{/*We can define new props for our component I'm mixin props from mui Alert and Snackbar, from Alert I'm using severity and from Snackbar the open and onClose and message goes inside the alert as a simple string :D */}
<Notification
open={open}
onClose={handleClose}
severity="error"
message="that is what I am talking about"/>
</div>
)
}
Now we just need to create the custom component that has multiple lil components inside that we can edit as we want!
//Notification.component.jsx file
import React, { forwardRef} from 'react';
import Snackbar from '#mui/material/Snackbar';
import MuiAlert from '#mui/material/Alert';
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});
const Notification= ({open, onClose, severity, message}) => {
/*<> ... </> The empty tag is shorthand for <React. Fragment>, allowing you to have multiple top-most elements without wrapping further HTML.
*/
return (
<>
<Snackbar open={open} autoHideDuration={6000} onClose={onClose}>
<Alert onClose={onClose} severity={severity} sx={{ width: '100%' }}>
{message}
</Alert>
</Snackbar>
</>
);
}
export default Notification;
But if you really want use typed values You can check the returned type in your VSCode for each variable and function. but I really don't see any benefit for that. But as it doesn't change the result I will not implement it for me neither here.
I highly recommend to watch this video series https://www.youtube.com/watch?v=Roxf91mp3kw&t=367s
I am fetching array of objects from an API endpoint,
I am using react-redux for state management.
I have two problems.
data gets fetched but only loads and displays after I refresh the page.
console logging single statement 4 times 2 blank array before setting state and 2 with data after setting state where it should have been only once I guess.
Following is my code.
Dashboard Action
import dashboardAPI from "../../../API/ShopUserAPI/dashboardAPI"
export const CURRENT_ORDER_LOAD = "CURRENT_ORDER_LOAD"
export const CURRENT_ORDER_FETCH = "CURRENT_ORDER_FETCH"
export const CURRENT_ORDER_ERROR = "CURRENT_ORDER_ERROR"
export const currentOrderAction = () => {
return dispatch => {
dispatch({ type: CURRENT_ORDER_LOAD, payload: '' })
dashboardAPI.get('/orders')
.then(resp => {
console.log(resp)
dispatch({ type: CURRENT_ORDER_FETCH, payload: resp.data.data.orders })
})
.catch(err => {
console.log(err)
dispatch({ type: CURRENT_ORDER_ERROR, payload: 'error occured. Retry !' })
})
}
}
Dashboard Reducer
import { CURRENT_ORDER_LOAD, CURRENT_ORDER_FETCH, CURRENT_ORDER_ERROR } from "../actions/dashboardActions";
const initialState = {
isLoading: true,
currentOrderData: [],
currentOrdererror: ''
}
const dashboardReducer = (state = initialState, action) => {
switch (action.type) {
case CURRENT_ORDER_LOAD:
return {
...state,
isLoading: true
}
case CURRENT_ORDER_FETCH:
return {
...state,
isLoading: false,
currentOrderData: action.payload
}
case CURRENT_ORDER_ERROR:
return {
...state,
isLoading: false,
currentOrdererror: action.payload
}
default:
return state
}
}
export default dashboardReducer
Dashboard page
import React from 'react'
import { Link } from 'react-router-dom'
import { makeStyles } from '#material-ui/core/styles';
import { Button, Typography, Grid, DialogContent, DialogActions, Dialog, DialogTitle, Divider, ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails } from "#material-ui/core";
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import { connect } from "react-redux";
const useStyles = makeStyles((theme) => ({
contentCenter: {
display: 'flex',
justifyContent: 'center',
marginTop: '1rem'
},
newSection: {
marginTop: '1rem'
},
innerSection: {
padding: '1rem'
},
heading: {
fontSize: theme.typography.pxToRem(15),
color: theme.palette.text.secondary,
},
secondaryHeading: {
fontSize: theme.typography.pxToRem(15),
color: theme.palette.text.secondary,
},
}))
const CurrentOrders = (props) => {
console.log(props.currentOrderData) // console.log printing 4 times
const [expanded, setExpanded] = React.useState(false);
const handleChange = (panel) => (event, isExpanded) => {
setExpanded(isExpanded ? panel : false);
};
const classes = useStyles()
return (
<div className={classes.newSection}>
{/* current orders list */}
<Typography className={classes.contentCenter}>
Current orders
</Typography>
{/* Orders expansion panel list */}
{props.currentOrderData.map(currentOrders => (
<ExpansionPanel expanded={expanded === 'panel1'} onChange={handleChange('panel1')} className={classes.newSection}>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Typography className={classes.heading}>Order ID: #{currentOrders.orderID}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Grid container justify="space-between">
<Grid item>
<Link to="/order" style={{ textDecoration: "none" }}>
<Button size="small" variant="contained" color="primary">
View Order
</Button>
</Link>
</Grid>
</Grid>
</ExpansionPanelDetails>
</ExpansionPanel>
))}
</div>
)
}
const mapStateToProps = state => {
return {
currentOrderData: state.dashboard.currentOrderData
}
}
export default connect(mapStateToProps)(CurrentOrders)
I've dispatched dashboard action in the parent component of this page
DIspatching of Dashboard
import React, { useEffect } from "react";
import { Container } from "#material-ui/core";
import CurrentOrders from "./CurrentOrders";
import TodaysOrders from "./TodaysOrders";
import { makeStyles } from '#material-ui/core/styles';
import { Button, Typography, Grid, DialogContent, DialogActions, Dialog, DialogTitle, Divider } from "#material-ui/core";
import { connect } from "react-redux";
import { currentOrderAction } from "../../../store/ShopUserStore/actions/dashboardActions";
const useStyles = makeStyles((theme) => ({
contentCenter: {
display: 'flex',
justifyContent: 'center',
marginTop: '1rem'
},
newSection: {
marginTop: '1rem'
},
innerSection: {
padding: '1rem'
},
buttonSuccess: {
color: theme.palette.success.main
}
}))
const Home = (props) => {
const [open, setOpen] = React.useState(false);
useEffect(() => {
props.getCurrentOrders()
})
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const classes = useStyles()
return (
<div>
<Container>
{/* shop status */}
<Grid container justify="flex-start" className={classes.newSection}>
<Button variant="outlined" color="primary" onClick={handleClickOpen} className={classes.buttonSuccess}>
Online
</Button>
</Grid>
{/* shop status confirmation dialog */}
<Dialog onClose={handleClose} aria-labelledby="customized-dialog-title" open={open}>
<DialogTitle id="customized-dialog-title" onClose={handleClose}>
Shop status
</DialogTitle>
<Divider />
<DialogContent>
<Typography className={classes.newSection}>
Are you sure that you want to change shop status ?
</Typography>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">
No
</Button>
<Button autoFocus onClick={handleClose} color="primary">
Yes
</Button>
</DialogActions>
</Dialog>
<Grid container spacing={3}
>
<Grid item xs={12} md={6}>
<CurrentOrders />
</Grid>
<Grid item xs={12} md={6}>
<TodaysOrders />
</Grid>
</Grid>
</Container>
</div>
)
}
const mapDispatchToProps = (dispatch) => {
return {
getCurrentOrders: () => dispatch(currentOrderAction())
}
}
export default connect(null, mapDispatchToProps)(Home)
When you run your application, you should stumble into a nasty loop. The effect hook runs when the component mounts but also when the component updates. Because we are setting the state after every data fetch, the component updates and the effect runs again. It fetches the data again and again. That's a bug and needs to be avoided. We only want to fetch data when the component mounts. That's why you can provide an empty array as second argument to the effect hook to avoid activating it on component updates but only for the mounting of the component.
Try this:
useEffect(async () => {
props.getCurrentOrders()
}, []);
I'm trying to convert this functional component to class based component. I have tried for several hours but could not find where to place these const variables in component. If someone could write it out in class based component it will highly appreciated.
const useStyles = makeStyles(theme => ({
typography: {
padding: theme.spacing(2),
},
}));
function SimplePopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
function handleClick(event) {
setAnchorEl(anchorEl ? null : event.currentTarget);
}
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : null;
return (
<div>
<Button aria-describedby={id} variant="contained" onClick={handleClick}>
Toggle Popper
</Button>
<Popper id={id} open={open} anchorEl={anchorEl} transition>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper>
<Typography className={classes.typography}>The content of the Popper.</Typography>
</Paper>
</Fade>
)}
</Popper>
</div>
);
}
export default SimplePopper;
import React, { Component } from "react";
import { createMuiTheme } from "#material-ui/core/styles";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import Fade from "#material-ui/core/Fade";
import Paper from "#material-ui/core/Paper";
import Popper from "#material-ui/core/Popper";
import { withStyles } from "#material-ui/styles";
const theme = createMuiTheme({
spacing: 4
});
const styles = {
typography: {
padding: theme.spacing(2)
}
};
class SimplePopper extends Component {
constructor(props) {
super(props);
this.state = { anchorEl: null, open: false };
}
flipOpen = () => this.setState({ ...this.state, open: !this.state.open });
handleClick = event => {
this.state.ancherEl
? this.setState({ anchorEl: null })
: this.setState({ anchorEl: event.currentTarget });
this.flipOpen();
};
render() {
const open = this.state.anchorEl === null ? false : true;
console.log(this.state.anchorEl);
console.log(this.state.open);
const id = this.state.open ? "simple-popper" : null;
const { classes } = this.props;
return (
<div>
<Button
aria-describedby={id}
variant="contained"
onClick={event => this.handleClick(event)}
>
Toggle Popper
</Button>
<Popper
id={id}
open={this.state.open}
anchorEl={this.state.anchorEl}
transition
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper>
<Typography className={classes.typography}>
The content of the Popper.
</Typography>
</Paper>
</Fade>
)}
</Popper>
</div>
);
}
}
export default withStyles(styles)(SimplePopper);
First thing one need to understand is, how class based and functional components work. Also, when and where you use it.
In short, I can say functional components are Used for presenting static data. And class based are Used for dynamic source of data.
Here are few links for your reference.
Class based component vs Functional components what is the difference ( Reactjs ) and React functional components vs classical components
To answer your specific question.
import React, { Component } from 'react';
import { withStyles, makeStyles } from '#material-ui/styles';
const useStyles = makeStyles(theme => ({
typography: {
padding: theme.spacing(2),
},
}));
class SimplePopper extends Component {
constructor(props){
super(props)
this.state = { anchorEl: null, setAnchorEl: null }; <--- Here see the state creation
this.handleClick= this.handleClick.bind(this);
}
handleClick(event) {
const { anchorEl, setAnchorEl } = this.state; <--- Here accessing the state
setAnchorEl(anchorEl ? null : event.currentTarget);
}
render() {
const { anchorEl, setAnchorEl } = this.state; <--- Here accessing the state
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : null;
const { classes } = this.props;
return (
<div>
............Rest of the JSX............
</div>
);
}
}
export default withStyles(useStyles)(SimplePopper);
Note that here I've used withStyles to wrap the style to your component. So, that the styles will be available as classNames.
Explore the difference and convert the rest
This is more enough to begin with.
I'm unable to input a value on the input field when selecting edit, i think it could be a onChange issue.
I look at something similar here, but the code seems to be outdated, and im using controlled components and not refs.
Editable.js this component renders an input field when edit is clicked
import React from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import TextField from '#material-ui/core/TextField';
const Editable = (props) => (
<div>
<TextField
id="outlined-name"
label="Title"
style={{width: 560}}
name="title"
value={props.editField}
onChange={props.onChange}
margin="normal"
variant="outlined"/>
</div>
)
export default Editable;
PostList.js renders a list of the post items
import React, { Component } from 'react';
import Paper from '#material-ui/core/Paper';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import {DeletePost} from '../actions/';
import Editable from './Editable';
const Styles = {
myPaper: {
margin: '20px 0px',
padding: '20px'
}
}
class PostList extends Component{
constructor(props){
super(props);
this.state ={
}
}
// Return a new function. Otherwise the DeletePost action will be dispatch each
// time the Component rerenders.
removePost = (id) => () => {
this.props.DeletePost(id);
}
onChange = (e) => {
e.preventDefault();
// maybe their is issue with it calling title from name in the editable
// component
this.setState({
[e.target.title]: e.target.value
})
}
render(){
const {posts, editForm, isEditing} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={i} style={Styles.myPaper}>
<Typography variant="h6" component="h3">
{/* if else teneray operator */}
{isEditing ? (
<Editable editField={post.title} onChange={this.onChange}/>
): (
<div>
{post.title}
</div>
)}
</Typography>
<Typography component="p">
{post.post_content}
<h5>
by: {post.username}</h5>
<Typography color="textSecondary">{moment(post.createdAt).calendar()}</Typography>
</Typography>
{!isEditing ? (
<Button variant="outlined" type="submit" onClick={editForm}>
Edit
</Button>
):(
<Button variant="outlined" type="submit" onClick={editForm}>
Update
</Button>
)}
<Button
variant="outlined"
color="primary"
type="submit"
onClick={this.removePost(post.id)}>
Remove
</Button>
</Paper>
))}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
// Pass id to the DeletePost functions.
DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(null, mapDispatchToProps)(PostList);
Posts.js
import React, { Component } from 'react';
import PostList from './PostList';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {GetPosts} from '../actions/';
const Styles = {
myPaper:{
margin: '20px 0px',
padding:'20px'
}
,
wrapper:{
padding:'0px 60px'
}
}
class Posts extends Component {
state = {
posts: [],
loading: true,
isEditing: false,
}
async componentWillMount(){
await this.props.GetPosts();
this.setState({ loading: false })
const reduxPosts = this.props.myPosts;
const ourPosts = reduxPosts
console.log(reduxPosts); // shows posts line 35
}
formEditing = () => {
if(this.state.isEditing){
this.setState({
isEditing: false
});
}
else{
this.setState({
isEditing:true
})
}
}
render() {
const {loading} = this.state;
const { myPosts} = this.props
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList isEditing={this.state.isEditing} editForm={this.formEditing} posts={myPosts}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
myPosts: state.post.posts
})
const mapDispatchToProps = (dispatch, state) => ({
GetPosts: () => dispatch( GetPosts())
});
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Posts));
You're setting the value of the input with the property this.props.posts[index].title but you're handling the change through the PostLists state.
You should either delegate the onChange function to the component that's passing the list to your PostList component or store and update the list through the PostLists state.
you need to pass the value being set in change function
onChange = (e) => {
e.preventDefault();
// maybe their is issue with it calling title from name in the editable
// component
this.setState({
[e.target.title]: e.target.value
})
}
youre setting the state of your edit field. You have to reference that value again when you reference your Editable.
<Editable editField={this.state.[here should be whatever e.target.title was for editable change event] } onChange={this.onChange}/>
in your editable component your setting the value to the prop editField.
<TextField
id="outlined-name"
label="Title"
style={{width: 560}}
name="title"
**value={props.editField}**
onChange={props.onChange}
margin="normal"
variant="outlined"/>
hope that helps
I am using social login in react-admin (former admin-on-rest) and I have the user picture from his social media, however I didn't find how to change the user profile image in the top right corner of the screen:
Is there any prop to be set, like custom login or custom logout button?
Thanks.
Currently, the process involves a lot of code as you'll have to rewrite the UserMenu completely. To use it, you'll also have to implement a custom Layout with a custom AppBar. The process will be simplified when https://github.com/marmelab/react-admin/pull/2391 will be merged.
// in src/MyUserMenu.js
import React, { Children, cloneElement } from 'react';
import PropTypes from 'prop-types';
import Tooltip from '#material-ui/core/Tooltip';
import IconButton from '#material-ui/core/IconButton';
import Menu from '#material-ui/core/Menu';
import AccountCircle from '#material-ui/icons/AccountCircle';
import { translate } from 'ra-core';
class UserMenu extends React.Component {
static propTypes = {
children: PropTypes.node,
label: PropTypes.string.isRequired,
logout: PropTypes.node,
translate: PropTypes.func.isRequired,
};
static defaultProps = {
label: 'ra.auth.user_menu',
};
state = {
auth: true,
anchorEl: null,
};
handleChange = (event, checked) => {
this.setState({ auth: checked });
};
handleMenu = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
render() {
const { children, label, logout, translate } = this.props;
if (!logout && !children) return null;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
return (
<div>
<Tooltip title={label && translate(label, { _: label })}>
<IconButton
arial-label={label && translate(label, { _: label })}
aria-owns={open ? 'menu-appbar' : null}
aria-haspopup="true"
onClick={this.handleMenu}
color="inherit"
>
{/* Replace this icon with whatever you want, a user avatar or another icon */}
<AccountCircle />
</IconButton>
</Tooltip>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={open}
onClose={this.handleClose}
>
{Children.map(children, menuItem =>
cloneElement(menuItem, { onClick: this.handleClose })
)}
{logout}
</Menu>
</div>
);
}
}
export default translate(UserMenu);
// in src/MyAppBar.js
import { AppBar } from 'react-admin';
import MyUserMenu from './MyUserMenu';
const MyAppBar = (props) => <AppBar {...props} userMenu={MyUserMenu} />;
// in src/MyLayout.js
import { Layout } from 'react-admin';
import MyAppBar from './MyAppBar';
const MyLayout = (props) => <Layout {...props} appBar={MyAppBar} />;
export default MyLayout;
Documentation: https://marmelab.com/react-admin/Theming.html#using-a-custom-appbar