Unable to open Modal in ReactJS - reactjs

Explanation
I am building a simple ReactJS web page where one can login/signup. I've built the home page and have a navbar with login and signup buttons on it. I'm using MaterialUI. I want the login modal to open when clicking on the login button. But till now, I've only been able to open the modal from a button directly inside the modal's code.
What I've done
I've researched a LOT on stackoverflow and the web and tried implementing a few of the approaches like refs in all different ways specified. I've tried reading the ReactJS documentation to understand the concepts.
Code:
I have a separate file for the Login Modal and for the Navbar. Currently, I'm exporting the LoginModal component into the Navbar file. Then exporting the Navbar component into the HomePage file.
Here is the navbar file's code (Navbar.js):
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import LoginModal from './LoginModal.js';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
navbar: {
backgroundColor: 'transparent',
boxShadow: 'none',
color: '#06ABFB'
}
};
class Navbar extends React.Component {
constructor(props) {
super(props);
this.loginmodal = React.createRef();
}
openLoginModal = () => {
console.log(this.loginmodal.current);
};
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<LoginModal ref={this.loginmodal} />
<AppBar position="static" className={classes.navbar}>
<Toolbar>
<Typography variant="title" color="inherit" className={classes.grow}>
WorkNet
</Typography>
<Button color="inherit" onClick={this.openLoginModal}>Login</Button>
<Button color="inherit">Sign Up</Button>
</Toolbar>
</AppBar>
</div>
);
}
}
Navbar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Navbar);
and here is the code for the login modal (LoginModal.js)
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import LoginModal from './LoginModal.js';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
navbar: {
backgroundColor: 'transparent',
boxShadow: 'none',
color: '#06ABFB'
}
};
class Navbar extends React.Component {
constructor(props) {
super(props);
this.loginmodal = React.createRef();
}
openLoginModal = () => {
console.log(this.loginmodal.current);
};
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<LoginModal ref={this.loginmodal} />
<AppBar position="static" className={classes.navbar}>
<Toolbar>
<Typography variant="title" color="inherit" className={classes.grow}>
WorkNet
</Typography>
<Button color="inherit" onClick={this.openLoginModal}>Login</Button>
<Button color="inherit">Sign Up</Button>
</Toolbar>
</AppBar>
</div>
);
}
}
Navbar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Navbar);

A few things to consider:
In openLoginModal, console.log is not going to do anything other than print to the console.
You probably do not need to use refs for this. Check this out!
Instead, you can set a state in Navbar.js with something like:
handleModal = () => {
this.setState({modalIsOpen: !this.state.modalIsOpen});
};
Then you can pass that to your Modal using props with something like:
<LoginModal open={this.state.modalIsOpen} onClose={this.handleModal} />
<Button color="inherit" onClick={this.handleModal}>Login</Button>

Related

Getting error when trying to use functional component in class component

I have two main components. The Recipe.js component which is the parent component, and RecipeCard.js which is the child. I am getting the error Error: Invalid hook call. Hooks can only be called inside of the body of a function component. I am not exactly sure as to why I am getting this error. I was hoping I could get some insight as to what the problem is.
Recipe.js:
import React, { Component } from "react";
import AddRecipe from "./addRecipe";
import "./Recipe.css";
import { Route, BrowserRouter as Router, Switch, Link } from "react-router-dom";
import { useAuth0 } from "#auth0/auth0-react";
import RecipeCard from "./RecipeCard";
class Recipe extends Component {
constructor() {
super();
this.state = {
recipes: [], // State array to hold recipe objects that are fetched from the database
search: ''
};
}
updateSearch(event) {
this.setState({ search: event.target.value.substr(0, 20) });
}
componentDidMount() {
fetch("/getRecipes") //Api call using route from server.js to obtain recipe data
.then((res) => res.json())
.then((recipes) =>
this.setState(
{ recipes },
() =>
//inserts data to the state array of the component
console.log("recipes fetched...", recipes) //confirm that the recipes were fetched in the console
)
);
}
render() {
let filteredRecipes = this.state.recipes.filter(
(recipe) => {
return recipe.recipeName.toLowerCase().indexOf(this.state.search) !== -1;
}
);
return (
<Router>
<div>
<input type="text" value={this.state.search} onChange={this.updateSearch.bind(this)}></input>
<ul>
{filteredRecipes.map((
recipe //iterate through each recipe object in the state array display the id, name and instructions of each recipe
) => (
<li className="Recipes" key={recipe.idrecipe}>
<RecipeCard imgUrl={recipe.imgUrl} id={recipe.idrecipe} name={recipe.recipeName} instructions={recipe.recipeInstruction} />
</li>
))}
</ul>
<Switch>
<Route path="/addRecipe">
<AddRecipe />
</Route>
</Switch>
</div>
</Router>
);
}
}
export default Recipe; //Export the recipe component to be used in the main index.js file
RecipeCard.js:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardActionArea from '#material-ui/core/CardActionArea';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import CardMedia from '#material-ui/core/CardMedia';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
const useStyles = makeStyles({
root: {
maxWidth: 345,
},
media: {
height: 140,
},
});
export default function RecipeCard(props) {
const classes = useStyles();
return (
<Card className={classes.root}>
<CardActionArea>
<CardMedia
className={classes.media}
image={props.imgUrl}
title=""
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{props.name}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{props.instructions}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" color="primary">
Share
</Button>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
);
}
Hooks can be only used in functional components.
Your Recipe.js is a class component which extends from React.Component.
That's why useAuth0 will fail since it is a hook.
You have two options now. You can either change your Recipe.js into a functional component or use an alternative to useAuth0 which I might guess is a Higher-Order-Component provided by #auth0/auth0-react.
withAuth0 might be a good alternative.
Refs :
https://auth0.com/docs/libraries/auth0-react#use-with-a-class-component
https://reactjs.org/docs/hooks-intro.htm

React passing down a function that modifies a third component

I have been trying to create a component per function in my app, but I am facing the following issue.
I have the component DisplayAllData that sends the data and an actionable button to DisplayDataWithButton, the issue is that when someone clicks on the Button send in the props, the function modifies the state of the parent component, which is also sent as a parameter to FullScreenDialog, and that throws a Warning: Cannot update a component while rendering a different component.
I designed the components in this particular way because:
DisplayAllData is the only function that has the data to render and the actionable button. (Model)
DisplayDataWithButton only renders the data and displays the actionable components for that particular data, in this case a button that opens a Dialog in screen. (Viewer)
You can find a running example here: https://codesandbox.io/s/material-demo-forked-8oyef
import React from "react";
import Button from "#material-ui/core/Button";
import DisplayDataWithButton from "./DisplayDataWithButton";
import FullScreenDialog from "./fullscreendialog";
export default function App(props) {
const [openFullScreen, setopenFullScreen] = React.useState(false);
var items = ["John", "Melinda"];
var dataDisplayFunction = (data) => {
return data.map((item) => {
return [
item,
<Button
color="success"
size="small"
className="px-2"
variant="contained"
onClick={setopenFullScreen()}
>
Show Dialog
</Button>
];
});
};
return (
<>
<DisplayDataWithButton
shapeDataFunction={dataDisplayFunction}
data={items}
/>
<FullScreenDialog open={openFullScreen} />
</>
);
}
DisplayDataWithButton.js
export default function DisplayDataWithButton(props) {
return props.shapeDataFunction(props.data);
}
I suspect that there is another way to implement this model, any suggestion, or ideas on how to fix this one.
Thanks
A couple of things: "I have been trying to create a component per function in my app". Forget that - the pattern you have opted for here is called render props but I don't see how it is necessary. Keep it simple. If a big component is simpler to understand than a small component I always opt for the bigger component. Splitting your components will not magically make them easier to understand.
All of the warnings have been dealt with. Most of them were simple mistakes, for example: onClick={setopenFullScreen()} should be onClick={setopenFullScreen}. You can compare your sandbox with my sandbox for all of the changes.
import React from "react";
import ReactDOM from "react-dom";
import Button from "#material-ui/core/Button";
import FullScreenDialog from "./fullscreendialog";
export default function App() {
const [openFullScreen, setopenFullScreen] = React.useState(false);
const items = ["John", "Melinda"];
return (
<>
{items.map((item) => [
item,
<Button
key={item}
color="primary"
size="small"
className="px-2"
variant="contained"
onClick={() => setopenFullScreen((prev) => !prev)}
>
Show Dialog
</Button>
])}
<FullScreenDialog open={openFullScreen} />
</>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
import React from "react";
import { makeStyles } from "#material-ui/core";
import Button from "#material-ui/core/Button";
import Dialog from "#material-ui/core/Dialog";
import ListItemText from "#material-ui/core/ListItemText";
import ListItem from "#material-ui/core/ListItem";
import List from "#material-ui/core/List";
import Divider from "#material-ui/core/Divider";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import IconButton from "#material-ui/core/IconButton";
import Typography from "#material-ui/core/Typography";
import CloseIcon from "#material-ui/icons/Close";
import Slide from "#material-ui/core/Slide";
const useStyles = makeStyles((theme) => ({
appBar: {
position: "relative"
},
title: {
marginLeft: theme.spacing(2),
flex: 1
}
}));
const Transition = React.forwardRef(function Transition(props, ref) {
return <Slide direction="up" ref={ref} {...props} />;
});
export default function FullScreenDialog(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Dialog
fullScreen
open={props.open}
onClose={handleClose}
TransitionComponent={Transition}
>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={handleClose}
aria-label="close"
>
<CloseIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
Sound
</Typography>
<Button autoFocus color="inherit" onClick={handleClose}>
save
</Button>
</Toolbar>
</AppBar>
<List>
<ListItem button>
<ListItemText primary="Phone ringtone" secondary="Titania" />
</ListItem>
<Divider />
<ListItem button>
<ListItemText
primary="Default notification ringtone"
secondary="Tethys"
/>
</ListItem>
</List>
</Dialog>
</div>
);
}

Converting class component (Navbar) to functional using hooks

I am converting the React + Material UI + Firebase project found on the Material UI doc's page. I am trying to open the SignUp Dialog (i.e. SignUp Modal). Here is a simplified version of the 4 corresponding files and I need help opening up the sign up modal.
App.js
import React, { useState } from 'react';
import { ThemeProvider } from '#material-ui/core/styles';
import Navbar from './components/Navbar'
import DialogHost from './components/DialogHost';
import Loading from './components/Loading'
import theme from './theme';
function App() {
const [signedIn] = useState(false)
const [ready] = useState(true) //!toggle for testing
const [dialog, setDialog] = useState({
isOpenSignUp: false,
isOpenSignIn: false
});
const openDialog = e => {
setDialog({...dialog,[e.target.name]: true});
}
const closeDialog = e => {
setDialog({...dialog,[e.target.name]: false});
}
return (
<ThemeProvider theme={theme}>
{!ready &&
<Loading />
}
{ready &&
<>
<Navbar
signedIn={signedIn}
onSignUpClick={openDialog}
onSignInClick={openDialog}
/>
<DialogHost
signedIn={signedIn}
dialogs={
{
signUpDialog: {
dialogProps: {
open: dialog.isOpenSignUp,
//onClose below hasn't been converted... not entirely sure how
onClose: (callback) => {
this.closeDialog('signUpDialog');
if (callback && typeof callback === 'function') {
callback();
}
}
}
},
}
}
/>
</>
}
</ThemeProvider>
);
}
export default App;
Navbar.js
import React from 'react'
import PropTypes from 'prop-types';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Box from '#material-ui/core/Box';
import Button from '#material-ui/core/Button';
const Navbar = ({ signedIn, onSignUpClick, onSignInClick }) => {
return (
<AppBar color="primary" position="static">
<Toolbar variant="regular">
<Box flexGrow={1}>
<Typography color="inherit" variant="h6">{process.env.REACT_APP_NAME}</Typography>
</Box>
{!signedIn &&
<>
<Box mr={1}>
<Button name="isOpenSignUp" color="secondary" variant="contained" onClick={onSignUpClick}>Sign Up</Button> //GAVE THE BUTTON NAMES, BUT THIS DOESN'T SEEM CORRECT...
</Box>
<Button name="isOpenSignIn" color="secondary" variant="contained" onClick={onSignInClick}>Sign In</Button>
</>
}
</Toolbar>
</AppBar>
)
}
Navbar.defaultProps = {
signedIn: false
};
Navbar.propTypes = {
signedIn: PropTypes.bool.isRequired
};
export default Navbar
DialogHost.js
import React from 'react'
import PropTypes from 'prop-types';
import Hidden from '#material-ui/core/Hidden';
import SignUpDialog from '../../pages/SignUpDialog';
const DialogHost = ({ signedIn, dialogs }) => {
const signUpDialog = dialogs.signUpDialog;
return (
<>
<Hidden xsDown>
{!signedIn &&
<>
<SignUpDialog
dialogProps={signUpDialog.dialogProps}
{...signUpDialog.props}
/>
</>
}
</Hidden>
<Hidden smUp>
{!signedIn &&
<>
<SignUpDialog
dialogProps={{
fullScreen: true,
...signUpDialog.dialogProps
}}
{...signUpDialog.props}
/>
</>
}
</Hidden>
</>
)
}
DialogHost.defaultProps = {
signedIn: false
};
DialogHost.propTypes = {
signedIn: PropTypes.bool.isRequired,
dialogs: PropTypes.object.isRequired
};
export default DialogHost
I also have a SignUpDialog.js file, but all it is is rendering a functional component with a title of 'Sign Up'

Next.js How to implement Material-ui for all pages?

Following the example in the next.js repo: https://github.com/zeit/next.js/tree/v3-beta/examples/with-material-ui
I can get Material-ui working for a single page. And i dont want to do the getInitialProps to set the userAgent prop on every page.
I tried making a HOC that I can use in my pages. But the getInitialProps only works for pages. so my MyMui component cant get the req object.
I tried creating the _document.js page, and wrapped the main with my MuiThemeProvider. But that gives me a bunch of errors.
So how can I get a clean solution to implement material-ui for all my pages?
I ran into a similar issue, and found a solution that looks like this:
// hocs/default-page.js
export default Page => class DefaultPage extends React.Component {
static getInitialProps(ctx) {
// Ensures material-ui renders the correct css prefixes server-side
let userAgent
if (process.browser) {
userAgent = navigator.userAgent
} else {
userAgent = ctx.req.headers['user-agent']
}
// Check if Page has a `getInitialProps`; if so, call it.
const pageProps = Page.getInitialProps && Page.getInitialProps(ctx);
// Return props.
return { ...pageProps, userAgent }
}
...
render() {
return (
<MuiThemeProvider ...>
<Page/>
</MuiThemeProvider>
);
}
}
// pages/index.js
import defaultPage from '../hocs/default-page';
const Page = () => <h1>Hello World</h1>;
Page.getInitialProps = () => { ... };
export default defaultPage(Page);
// Also works for proper components:
export default defaultPage(class MyPage extends React.Component {
static getInitialProps() {
...
}
render() {
...
}
});
layout.js
/**
* Created by Counter on 5/30/2017.
*/
import React, {Component} from 'react'
import Head from 'next/head'
import RaisedButton from 'material-ui/RaisedButton'
import Dialog from 'material-ui/Dialog'
import Paper from 'material-ui/Paper';
import FlatButton from 'material-ui/FlatButton'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import injectTapEventPlugin from 'react-tap-event-plugin'
import {blue100, blue400, blue700} from 'material-ui/styles/colors';
import AppBar from 'material-ui/AppBar';
import TextField from 'material-ui/TextField';
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
try {
if (typeof window !== 'undefined') {
injectTapEventPlugin()
}
} catch (e) {
// do nothing
}
const styles = {
}
const _muiTheme = getMuiTheme({
palette: {
primary1Color: blue400,
primary2Color: blue700,
primary3Color: blue100,
},
});
class Layout extends Component {
render () {
const { userAgent,children } = this.props
/* https://github.com/callemall/material-ui/issues/3336 */
const muiTheme = getMuiTheme(getMuiTheme({userAgent: userAgent}), _muiTheme)
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div>
<Head>
<meta name="viewport" content="initial-scale=1.0, width=device-width"/>
<link rel='stylesheet' href='/static/css/react-md.light_blue-yellow.min.css'/>
</Head>
{children}
</div>
</MuiThemeProvider>
)
}
}
export default Layout
index.js
import React, {Component} from 'react'
import RaisedButton from 'material-ui/RaisedButton'
import Dialog from 'material-ui/Dialog'
import Paper from 'material-ui/Paper';
import FlatButton from 'material-ui/FlatButton'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import injectTapEventPlugin from 'react-tap-event-plugin'
import {blue100,blue400, blue500, blue700,orange500} from 'material-ui/styles/colors';
import Layout from '../component/layout'
import AppBar from 'material-ui/AppBar';
import TextField from 'material-ui/TextField';
import {Tabs, Tab} from 'material-ui/Tabs';
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
try {
if (typeof window !== 'undefined') {
injectTapEventPlugin()
}
} catch (e) {
// do nothing
}
const styles = {
headline: {
fontSize: 24,
paddingTop: 16,
marginBottom: 12,
fontWeight: 400,
},
contentContainerStyle:{
width:300,
marginTop:133,
float:'left'
},
floatingLabelStyle: {
color: orange500,
},
floatingLabelFocusStyle: {
color: blue500,
},
tabDiv:{
textAlign:'center',
backgroundColor: 'rgb(242, 244, 255);',
boxShadow: '10px 10px 5px #888888',
minHeight:350
},
button: {
margin: 12,
},
descDiv:{
width: '413px',
float: 'left',
margin: '133px 450px 0 25px',
},
outerDiv:{
display:'inline-block'
}
};
const _muiTheme = getMuiTheme({
palette: {
primary1Color: blue400,
primary2Color: blue700,
primary3Color: blue100,
},
});
class Main extends Component {
static getInitialProps ({ req }) {
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
const isServer = !!req
return {isServer, userAgent}
}
constructor (props, context) {
super(props, context);
this.state = {
clicked: false
}
}
render () {
const { userAgent } = this.props;
return(
<Layout userAgent={userAgent} >
<div style={styles.outerDiv}>
<div style={styles.descDiv}>
<p>Lorem Ipsum is simply dummy text of the printing and
typesetting industry. Lorem Ipsum has been the
industry's standard dummy text ever since the 1500s,
when an unknown printer took a galley of type and
scrambled it to make a type specimen book. It has
survived not only five centuries, but also the leap
into electronic typesetting, remaining essentially
unchanged. It was popularised in the 1960s with the
release of Letraset sheets containing Lorem </p>
<RaisedButton
target="_blank"
label="Test Your Api"
primary={true}
style={styles.button}
icon=''
/>
</div>
<Tabs
value={this.state.value}
onChange={this.handleChange}
style={styles.contentContainerStyle}
>
<Tab label="Login" value="a">
<div style={styles.tabDiv}>
<TextField
floatingLabelText="Username"
floatingLabelStyle={styles.floatingLabelStyle}
floatingLabelFocusStyle={styles.floatingLabelFocusStyle}
/>
<TextField
floatingLabelText="Password"
floatingLabelStyle={styles.floatingLabelStyle}
floatingLabelFocusStyle={styles.floatingLabelFocusStyle}
/>
<br/>
<RaisedButton
target="_blank"
label="Submit"
secondary={true}
style={styles.button}
icon=''
/>
</div>
</Tab>
<Tab label="SignUp" value="b">
<div style={styles.tabDiv}>
<TextField
floatingLabelText="Username"
floatingLabelStyle={styles.floatingLabelStyle}
floatingLabelFocusStyle={styles.floatingLabelFocusStyle}
/>
<TextField
floatingLabelText="Password"
floatingLabelStyle={styles.floatingLabelStyle}
floatingLabelFocusStyle={styles.floatingLabelFocusStyle}
/>
<TextField
floatingLabelText="Confirm Password"
floatingLabelStyle={styles.floatingLabelStyle}
floatingLabelFocusStyle={styles.floatingLabelFocusStyle}
/>
<br/>
<RaisedButton
target="_blank"
label="Submit"
secondary={true}
style={styles.button}
icon=''
/>
</div>
</Tab>
</Tabs>
</div>
</Layout>
)
}
}
export default Main
above files are from my project . i have created layout.js file which i have used in all the pages . this is one way to do it .

implement BackButton in react-admin

I need to implement a <BackButton /> in react-admin for example when I open show page for a resource I need able to back to the list page.
Can you guide me to implement this?
I'm not familiar with react-admin routing mechanism.
Now I'm using this component in my edit form actions props:
const MyActions = ({ basePath, data, resource }) => (
<CardActions>
<ShowButton basePath={basePath} record={data} />
<CloneButton basePath={basePath} record={data} />
{/* Need <BackButton /> here */}
</CardActions>
);
export const BookEdit = (props) => (
<Edit actions={<MyActions />} {...props}>
<SimpleForm>
...
</SimpleForm>
</Edit>
);
You can use react-router-redux's goBack() function to achieve this.
For example, your button component will look something like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Button from '#material-ui/core/Button';
import { goBack } from 'react-router-redux';
class BackButton extends Component {
handleClick = () => {
this.props.goBack();
};
render() {
return <Button variant="contained" color="primary" onClick={this.handleClick}>Go Back</Button>;
}
}
export default connect(null, {
goBack,
})(BackButton);
Now use that button component in your CardActions.
You can get help from an example which uses react-router-redux's push() function in a similar way from the official docs.
Create a back button. This one will pass props and children (text) and uses react-router directly, which I think makes more sense and keeps your code simple.
// BackButton.js
import React from 'react'
import Button from '#material-ui/core/Button'
import { withRouter } from 'react-router'
const BackButton = ({ history: { goBack }, children, ...props }) => (
<Button {...props} onClick={goBack}>
{children}
</Button>
)
export default withRouter(BackButton)
Example usage:
import { Toolbar, SaveButton } from 'react-admin'
import BackButton from './BackButton'
const SomeToolbar = props => (
<Toolbar {...props}>
<SaveButton />
<BackButton
variant='outlined'
color='secondary'
style={{ marginLeft: '1rem' }}
>
Cancel
</BackButton>
</Toolbar>
)
The complete code is below.
//BackButton.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import compose from 'recompose/compose';
import { withStyles, createStyles } from '#material-ui/core/styles';
import { translate } from 'ra-core';
import Button from '#material-ui/core/Button';
import ArrowBack from '#material-ui/icons/ArrowBack';
import classnames from 'classnames';
import { fade } from '#material-ui/core/styles/colorManipulator';
const styles = theme =>
createStyles({
deleteButton: {
color: theme.palette.error.main,
'&:hover': {
backgroundColor: fade(theme.palette.error.main, 0.12),
// Reset on mouse devices
'#media (hover: none)': {
backgroundColor: 'transparent',
},
},
},
});
const sanitizeRestProps = ({
basePath,
className,
classes,
label,
invalid,
variant,
translate,
handleSubmit,
handleSubmitWithRedirect,
submitOnEnter,
record,
redirect,
resource,
locale,
...rest
}) => rest;
class BackButton extends Component {
static contextTypes = {
router: () => true, // replace with PropTypes.object if you use them
}
static propTypes = {
label: PropTypes.string,
refreshView: PropTypes.func.isRequired,
icon: PropTypes.element,
};
static defaultProps = {
label: 'ra.action.back',
icon: <ArrowBack />,
};
render() {
const {
className,
classes = {},
invalid,
label = 'ra.action.back',
pristine,
redirect,
saving,
submitOnEnter,
translate,
icon,
onClick,
...rest
} = this.props;
return (
<Button
onClick={this.context.router.history.goBack}
label={label}
className={classnames(
'ra-back-button',
classes.backButton,
className
)}
key="button"
{...sanitizeRestProps(rest)}>
{icon} {label && translate(label, { _: label })}
</Button>
)
}
}
const enhance = compose(
withStyles(styles),
translate
);
export default enhance(BackButton);
//Toolbar.js
import React from 'react';
import {
Toolbar,
SaveButton,
DeleteButton,
} from 'react-admin';
import { withStyles } from '#material-ui/core';
import BackButton from './BackButton'
const toolbarStyles = {
toolbar: {
display: 'flex',
justifyContent: 'space-between',
},
};
export const CustomEditToolbar = withStyles(toolbarStyles)(props => (
<Toolbar {...props}>
<SaveButton/>
<DeleteButton/>
<BackButton/>
</Toolbar>
));
export const CustomCreateToolbar = withStyles(toolbarStyles)(props => (
<Toolbar {...props}>
<SaveButton/>
<BackButton/>
</Toolbar>
));

Resources