I am attempting to recreate the theme toggle feature that the material-ui website.
My Github Repo: https://github.com/jonnyg23/flask-rest-ecommerce/tree/next-app-migration
So far, I have noticed that material-ui's website uses a cookie called paletteType in order to store the client's theme choice. I understand that a Context Provider should be used to set the cookie, however, my nav-bar implementation has an issue changing themes after the second click of my theme toggle button.
Any help would be greatly appreciated, thank you.
CustomThemeProvider.js:
import React, { createContext, useState } from "react";
import { ThemeProvider } from "#material-ui/core/styles";
import getTheme from "../themes";
import Cookie from "js-cookie";
export const CustomThemeContext = createContext({
// Set the default theme and setter.
appTheme: "light",
setTheme: null,
});
const CustomThemeProvider = ({ children, initialAppTheme }) => {
// State to hold selected theme
const [themeName, _setThemeName] = useState(initialAppTheme);
// Retrieve theme object by theme name
const theme = getTheme(themeName);
// Wrap setThemeName to store new theme names as cookie.
const setThemeName = (name) => {
// console.log("CustomThemeProvider, SetThemeName", name);
Cookie.set("appTheme", name);
_setThemeName(name);
};
const contextValue = {
appTheme: themeName,
setTheme: setThemeName,
};
return (
<CustomThemeContext.Provider value={contextValue}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</CustomThemeContext.Provider>
);
};
export default CustomThemeProvider;
_app.js:
import "../styles/globals.css";
import React, { useContext, useEffect } from "react";
import PropTypes from "prop-types";
import { useAuth0 } from "#auth0/auth0-react";
import Head from "next/head";
import { Provider as NextAuthProvider } from "next-auth/client";
import { makeStyles } from "#material-ui/core/styles";
import CssBaseline from "#material-ui/core/CssBaseline";
import Auth0ProviderWithHistory from "../auth/auth0-provider-with-history";
import CustomThemeProvider, {
CustomThemeContext,
} from "../context/CustomThemeProvider";
export default function App({ Component, pageProps }) {
// const { isLoading } = useAuth0();
const ThemeContext = useContext(CustomThemeContext);
useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<React.Fragment>
<Head>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
</Head>
<CustomThemeProvider initialAppTheme={ThemeContext.appTheme}>
<Auth0ProviderWithHistory>
<NextAuthProvider session={pageProps.session}>
<CssBaseline />
<Component {...pageProps} />
</NextAuthProvider>
</Auth0ProviderWithHistory>
</CustomThemeProvider>
</React.Fragment>
);
}
App.propTypes = {
Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired,
};
ThemeModeToggle.js:
import React, { useContext } from "react";
import { IconButton } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import Brightness5TwoToneIcon from "#material-ui/icons/Brightness5TwoTone";
import Brightness2TwoToneIcon from "#material-ui/icons/Brightness2TwoTone";
import { CustomThemeContext } from "../context/CustomThemeProvider";
const useStyles = makeStyles((theme) => ({
light: {
color: theme.palette.secondary.main,
},
dark: {
color: theme.palette.secondary.main,
},
}));
const ThemeModeToggle = ({ fontSize }) => {
const classes = useStyles();
const { appTheme, setTheme } = useContext(CustomThemeContext);
// console.log("ThemeModeToggle", appTheme);
const handleThemeChange = (appTheme, setTheme) => {
if (appTheme === "light") {
setTheme("dark");
} else {
setTheme("light");
}
};
return (
<IconButton onClick={() => handleThemeChange(appTheme, setTheme)}>
{appTheme === "light" ? (
<Brightness5TwoToneIcon fontSize={fontSize} className={classes.light} />
) : (
<Brightness2TwoToneIcon fontSize={fontSize} className={classes.dark} />
)}
</IconButton>
);
};
export default ThemeModeToggle;
Solved, I found the issue!
It turns out that my page components such as StorefrontIcon and the AppBar colors had to be explicitly added with className={classes.NAME} or with style={{ backroundColor: theme.palette.primary.main }} for example. Both methods are shown below in my nav-bar.js file.
import React from "react";
import { AppBar, Grid, Container, Toolbar } from "#material-ui/core";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import StorefrontIcon from "#material-ui/icons/Storefront";
import MainNav from "./main-nav";
import AuthNav from "./auth-nav";
import ThemeModeToggle from "./ThemeModeToggle";
import { Desktop, SmallScreen } from "./Responsive";
import HamburgerMenu from "./HamburgerMenu";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.down("sm")]: {
padding: 0,
},
},
icon: {
color: theme.palette.primary.contrastText,
},
}));
const NavBar = () => {
const classes = useStyles();
const theme = useTheme();
return (
<AppBar
position="static"
elevation={3}
style={{ backgroundColor: theme.palette.primary.main }}
>
<Toolbar>
<Container className={classes.root}>
<Grid container justify="center" alignItems="center" spacing={2}>
<Desktop>
<Grid item xs={1}>
<StorefrontIcon fontSize="large" className={classes.icon} />
</Grid>
<Grid item xs={7}>
<MainNav />
</Grid>
<Grid item xs={3}>
<AuthNav />
</Grid>
<Grid item xs={1}>
<ThemeModeToggle fontSize="large" />
</Grid>
</Desktop>
<SmallScreen>
<Grid item xs={2}>
<StorefrontIcon fontSize="medium" className={classes.icon} />
</Grid>
<Grid container item xs={10} justify="flex-end">
<HamburgerMenu />
</Grid>
</SmallScreen>
</Grid>
</Container>
</Toolbar>
</AppBar>
);
};
export default NavBar;
Related
I have this problem upgrading my react application. Basically, I followed the damn mui-v5 documentation, and I still have some erros in my app.tsx file.
import { useReactOidc, withOidcSecure } from '#axa-fr/react-oidc-context';
import { Container } from '#mui/material';
import CssBaseline from '#mui/material/CssBaseline';
import {
ThemeProvider,
StyledEngineProvider,
Theme,
} from '#mui/material/styles';
import createStyles from '#mui/styles/createStyles';
import makeStyles from '#mui/styles/makeStyles';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Redirect, Route, Switch } from 'react-router-dom';
import { RootState } from 'src/lib/configuration/store-configuration';
import { DarkTheme, LightTheme } from 'src/components/material-ui-theme';
import routes from 'src/components/routes/routes';
import BreadCrumbs from 'src/components/shared/breadcrumbs/breadcrumbs';
import ErrorBoundary from 'src/components/shared/error/Error';
import Footer from 'src/components/shared/footer/footer';
import Header from 'src/components/shared/header/header';
import Notifier from 'src/components/shared/notifications';
import ScrollTop from 'src/components/shared/scroll-top/scroll-top';
import Sider from 'src/components/shared/sider';
import { useTranslation } from 'react-i18next';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
display: 'flex',
},
toolbar: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
},
container: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(1),
},
}),
);
const App = () => {
const classes = useStyles();
const { oidcUser } = useReactOidc();
const appStore = useSelector((store: RootState) => store.app);
const [drawerOpen, setDrawerOpen] = useState(false);
const handleDrawerToggle = () => {
setDrawerOpen(!drawerOpen);
};
const { i18n } = useTranslation();
useEffect(() => {
document.documentElement.lang = i18n.language;
}, [i18n.language]);
return (
<StyledEngineProvider injectFirst>
<ThemeProvider
theme={
appStore.theme === 'dark' ? DarkTheme : LightTheme
}
>
<div className={classes.root}>
<CssBaseline />
<Header handleDrawerToggle={handleDrawerToggle} />
{oidcUser ? (
<Sider
drawerOpen={drawerOpen}
handleDrawerToggle={
handleDrawerToggle
}
/>
) : null}
<main className={classes.content}>
<div id="back-to-top-anchor" />
<div className={classes.toolbar} />
<Container
maxWidth="xl"
className={classes.container}
>
<Notifier />
<BreadCrumbs />
<ErrorBoundary>
<Switch>
{routes.map(
(route, i) => (
<Route
key={
'route_' +
i
}
exact={
route.exact
}
path={
route.path
}
component={
route.secure
? withOidcSecure(
route.component,
)
: route.component
}
/>
),
)}
<Redirect to="/" />
</Switch>
</ErrorBoundary>
</Container>
<ScrollTop />
<Footer />
</main>
</div>
</ThemeProvider>
</StyledEngineProvider>
);
};
export default App;
Here is the navigator console error :
I tried to modify some imports and used:
import { createTheme } from '#material-ui/core';
const theme = createTheme();
And passing it to makestyles((theme) => ... but nothing changed.
Please guys I need your support ! :)
link for the open-source github project: https://github.com/InseeFr/sugoi-ui
I am using nextjs and mui. I am facing a warning when rendering pages. Here is my code. Please help to solve the issue!!!
import "../styles/globals.scss";
import { AppProps } from "next/app";
import useGetAuthentication from "../hooks/useGetAuthentication";
import States from "../interfaces/states";
import STATUS from "../constants/status";
import { CssBaseline } from "#mui/material";
import { ThemeProvider } from "#mui/material/styles";
import theme from "../styles/theme";
import Layout from "../layouts/Layout";
import Head from "next/head";
import * as React from "react";
import Login from "../components/Login";
import { Box } from "#mui/material";
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
const checkStatusCode = (statusCode: number): boolean => {
return statusCode === STATUS.NOT_FOUND || statusCode === STATUS.INTERNAL_SERVER_ERROR;
};
function App({ Component, pageProps }: AppProps) {
const { states } = pageProps;
const { statusCode } = pageProps;
const { isAuthorized } = useGetAuthentication(states as States);
console.log("App -> Component", Component);
console.log("App -> pageProps", pageProps);
console.log("App -> states", states);
console.log("App -> statusCode", statusCode);
const drawerWidth: number = 240;
if (!checkStatusCode(statusCode) && !isAuthorized)
return (
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { lg: "230px", sm: `calc(100% - ${drawerWidth}px)` }
}}
>
<Login />
</Box>
);
return (
<>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<Layout>
<Component {...pageProps} />
</Layout>
</ThemeProvider>
</>
);
}
export default App;
Login component is below
import React, { useEffect } from "react";
import authenticationStore from "../../stores/persistences/authenticationStore";
import TestHttp from "../../httpModules/testHttp";
import STATUS from "../../constants/status";
import RequestSignIn from "../../interfaces/test/requestSignIn";
import styles from "./login.module.scss";
import { Alert, FormControlLabel, Grid, Paper, TextField, Typography, Stack, Button, Checkbox } from "#mui/material";
import Image from "next/image";
import useLoginInputs from "../../hooks/useLoginInputs";
import LocalStorageHandler from "../../utils/localStorageHandler";
import RememberId from "../../interfaces/rememberId";
import ERROR_MESSAGE from "../../constants/errorMessage";
import LOGIN_INFO from "../../constants/loginInfo";
const Login: React.FC = () => {
const localStorageHandler = new LocalStorageHandler<RememberId>();
const authorize = authenticationStore((state) => state.authorize);
const testHttp = new TestHttp();
const { inputs, setInputs, isRememberChecked, isError, setIsError, isIdEmpty, setIsIdEmpty, isPasswordEmpty, setIsPasswordEmpty, inputsHandler, checkboxHandler } = useLoginInputs([
"id",
"password"
]);
const { id, password } = inputs;
const onLoginHandler = async (): Promise<void> => {
if (isIdEmpty) {
return setIsError(ERROR_MESSAGE.ID_EMPTY);
}
if (isPasswordEmpty) {
return setIsError(ERROR_MESSAGE.PASSWORD_EMPTY);
}
const signInInfo: RequestSignIn = { id, password };
const { statusCode, jsonResult } = await testHttp.signIn(false, signInInfo);
/*
* 인증 실패 (아이디, 비밀번호 일치 하지 않는 경우 등) 발생 시 코드 작성
*/
if (statusCode !== STATUS.OK) {
setIsError(true);
return;
}
const { userInfo, tokenInfo } = jsonResult;
authorize(statusCode, userInfo, tokenInfo);
if (!isRememberChecked) return localStorageHandler.removeLocalStorageData(LOGIN_INFO.REMEMBER_ID);
localStorageHandler.setLocalStorageData("rememberId", {
id
});
};
useEffect(() => {
console.log("하이");
setIsIdEmpty(id.length <= 0);
setIsPasswordEmpty(password.length <= 0);
setIsError(null);
}, [id, password]);
console.log("id", id);
console.log("password", password);
return (
<Grid>
<Paper elevation={10} className={styles.container}>
<Grid align={"center"}>
<div className={styles.logo}>
<Image src={"/images/logo.svg"} width={"200px"} height={"80px"} alt={"logo"} />
<Typography variant={"h6"}>관리자</Typography>
</div>
</Grid>
<Stack spacing={1} justifyContent={"center"} alignItems={"center"} className={styles["login-container"]}>
<TextField name={"id"} placeholder={"아이디를 입력해주세요."} required value={id} type={"text"} className={styles["login-input"]} onChange={inputsHandler} />
<TextField name={"password"} placeholder={"비밀번호를 입력하세요."} required value={password} type={"password"} className={styles["login-input"]} onChange={inputsHandler} />
</Stack>
<Stack>
<FormControlLabel control={<Checkbox checked={isRememberChecked} />} label={"아이디 저장"} className={styles.checkbox} onChange={checkboxHandler} />
</Stack>
<Stack justifyContent={"center"} alignItems={"center"}>
<Button type={"submit"} color={"primary"} variant={"contained"} className={styles["login-button"]} size={"large"} onClick={onLoginHandler}>
로그인
</Button>
</Stack>
<Stack justifyContent={"center"} alignItems={"center"} className={styles["error-message"]}>
<div>
{isError && (
<Alert severity={"error"}>
<strong>{isError}</strong>
</Alert>
)}
</div>
</Stack>
</Paper>
</Grid>
);
};
export default Login;
the warning is
Warning: Expected server HTML to contain a matching <div> in <div>.
at div
at eval (webpack-internal:///./node_modules/#emotion/react/dist/emotion-element-cbed451f.browser.esm.js:57:66)
at Box (webpack-internal:///./node_modules/#mui/system/esm/createBox.js:36:72)
at Layout (webpack-internal:///./layouts/Layout/index.tsx:16:26)
at InnerThemeProvider (webpack-internal:///./node_modules/#mui/system/esm/ThemeProvider/ThemeProvider.js:21:70)
at ThemeProvider (webpack-internal:///./node_modules/#mui/private-theming/ThemeProvider/ThemeProvider.js:47:5)
at ThemeProvider (webpack-internal:///./node_modules/#mui/system/esm/ThemeProvider/ThemeProvider.js:41:5)
at App (webpack-internal:///./pages/_app.tsx:61:27)
at ErrorBoundary (webpack-internal:///./node_modules/next/dist/compiled/#next/react-dev-overlay/client.js:8:20638)
at ReactDevOverlay (webpack-internal:///./node_modules/next/dist/compiled/#next/react-dev-overlay/client.js:8:23179)
at Container (webpack-internal:///./node_modules/next/dist/client/index.js:323:9)
at AppContainer (webpack-internal:///./node_modules/next/dist/client/index.js:820:26)
at Root (webpack-internal:///./node_modules/next/dist/client/index.js:944:27)
window.console.error # next-dev.js?3515:25
Case 1
Most likely a Server<>Client out of date issue.
Fix
If you are using the development server > Restart it.
If you are getting this production > Rebuild + Restart.
Case 2
The components you are using render differently (due to bad coding) on the Server (SSR) vs Client (CSR). This can be silenced by adding suppressHydrationWarning={true} to the offending component.
Case 3
Another case I've seen is that someone has set dangerouslySetInnerHtml with invalid HTML. The fix is to correct the HTML OR silence it as we did in case 2.
In my case I tried to run localStorage getItem method outside useEffect hook.
So probably you can place your async code inside that hook.
In progress value update from use hook can also causing this problem. To prevent it, just copy the variable value from hook to state from useEffect hook. Here is the example
import { useAccount } from "wagmi";
...
const { address, isConnected, isConnecting } = useAccount();
// the value of these variable above are changed dynamically
const [connectionStat, setConnectionStat] = useState();
const [addr, setAddr] = useState();
// copy the value to state here
useEffect(() => {
setConnectionStat(isConnected);
setAddr(address);
}, [address, isConnected])
// then now we can display the value properly
return (
<div>
<p>Connection status : {connectionStat}</p>
<p>Connected to : {addr}</p>
....
</div>
);
Explanation: passing value directly from hook to JSX/view may causing inconsistent value inside JSX/view so the the page render can not be consistent as well and there will be different value between value in SSR and client.
Hope it helps.
I am trying to translate and position the Badge component by overriding the root CSS style, but for some reason it's not taking anything into effect. My Card component seems to be seeing the styling via the className prop, but I for some reason I the Badge component isn't seeing anything. I am following the documentation here.
Here's my component:
import React, { useState } from "react";
import PropTypes from "prop-types";
import Card from "#material-ui/core/Card";
import { Badge } from "#material-ui/core";
import CardHeader from "#material-ui/core/CardHeader";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles(theme => ({
root: {
anchorOriginTopRightRectangle: {
transform: "translate(-100%, -50%)"
}
},
card: {
maxWidth: 345
},
}));
const CardItem = ({
name,
discount
}) => {
const classes = useStyles();
return (
<Card className={classes.card}>
<CardHeader
title={
<>
{name}
<Badge
badgeContent={`-10%`}
color="error"
></Badge>
</>
}
subheader={"$1234"}
/>
// ... Card content
</Card>
);
};
export default(CardItem);
import React, { useState } from "react";
import PropTypes from "prop-types";
import Card from "#material-ui/core/Card";
import { Badge } from "#material-ui/core";
import CardHeader from "#material-ui/core/CardHeader";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles(theme => ({
anchorTopRight: {
transform: "translate(-100%, -50%)"
},
card: {
maxWidth: 345
},
}));
const CardItem = ({
name,
discount
}) => {
const classes = useStyles();
return (
<Card className={classes.card}>
<CardHeader
title={
<>
{name}
<Badge
classes={{ anchorOriginTopRightRectangle: classes.anchorTopRight}} // <== Working Code
badgeContent={`-10%`}
color="error"
></Badge>
</>
}
subheader={"$1234"}
/>
// ... Card content
</Card>
);
};
export default(CardItem);
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'
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>
));