I followed many solutions like this https://stackoverflow.com/a/71081567/19460332 and the official solution for this https://github.com/mui/material-ui/blob/master/examples/nextjs but nothing worked for me. I am using nextjs , material ui and js-cookies
the issue I am facing is for the MUI Switch
have a look to my code here https://github.com/MaheshYadavGitHub/pizza-gallery:
Here is the code for the section that is breaking :
below is the Layout.js where the Switch to toggle darkMode is not working as expected. The darkTheme gets applied as it gets from the cookies but the switch button still stays in off position on the first render but after another render it works perfectly and when again manually refreshed it does the same behaviour :-
import { useState, useContext } from "react";
import {
Container,
AppBar,
Toolbar,
Typography,
Link,
Switch,
CssBaseline,
ThemeProvider,
createTheme,
} from "#material-ui/core";
import { red } from "#material-ui/core/colors";
import Head from "next/head";
import Image from "next/image";
import NextLink from "next/link";
import useStyles from "../utils/styles";
import { Store } from "../utils/Store";
import Cookies from "js-cookie";
const Layout = ({ title, children }) => {
const { state, dispatch } = useContext(Store);
const { darkMode } = state;
const classes = useStyles();
const theme = createTheme({
palette: {
type: darkMode ? "dark" : "light",
primary: {
main: "#556cd6",
},
secondary: {
main: "#19857b",
},
error: {
main: red.A400,
},
},
typography: {
fontFamily: ["Arial", "Roboto Condensed"].join(","),
fontWeight: 700,
h1: {
fontSize: "1.6rem",
margin: "1rem 0rem",
fontWeight: 600,
"#media (min-width:600px)": {
fontSize: "2.6rem",
},
},
h6: {
fontWeight: 700,
},
navLinks: {
color: "#fff",
},
},
});
const handleThemeChange = (event) => {
dispatch({ type: darkMode ? "DARK_MODE_OFF" : "DARK_MODE_ON" });
const newThemeMode = !darkMode;
Cookies.set("darkMode", newThemeMode ? "ON" : "OFF");
};
return (
<>
<Head>
<title>{title ? `${title} - Pizza Gallery` : "Pizza Gallery"}</title>
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<AppBar position="static" className={classes.navbar}>
<Toolbar>
<NextLink href="/" passHref>
<Link>
<Typography
variant="h6"
className={classes.brand}
component="div"
>
Pizza Gallery
</Typography>
</Link>
</NextLink>
<div className={classes.grow}></div>
<Switch
id="switch"
checked={darkMode}
onChange={handleThemeChange}
color="primary"
></Switch>
<NextLink href="/login" passHref>
<Link>
<Typography>Login</Typography>
</Link>
</NextLink>
<NextLink href="/cart" passHref>
<Link>
<Typography>Cart</Typography>
</Link>
</NextLink>
</Toolbar>
</AppBar>
<Container className={classes.main}>{children}</Container>
<footer className={classes.footer}>
<Typography>all rights reserved © pizza gallery 2022</Typography>
</footer>
</ThemeProvider>
</>
);
};
export default Layout;
below is the Store :-
import Cookies from "js-cookie";
import { createContext, useReducer } from "react";
export const Store = createContext();
const initialState = {
darkMode: Cookies.get("darkMode") === "ON" ? true : false,
};
const reducer = (state, action) => {
switch (action.type) {
case "DARK_MODE_ON":
return { ...state, darkMode: true };
case "DARK_MODE_OFF":
return { ...state, darkMode: false };
default:
return state;
}
};
export const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const value = { state, dispatch };
return <Store.Provider value={value}>{children}</Store.Provider>;
};
/pages/_app.js
import { useEffect } from "react";
import "../styles/globals.css";
import { StoreProvider } from "../utils/Store";
function MyApp({ Component, pageProps }) {
useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<StoreProvider>
<Component {...pageProps} />
</StoreProvider>
);
}
export default MyApp;
/pages/_document.js
import { ServerStyleSheets } from "#material-ui/core/styles";
import Document, { Html, Head, Main, NextScript } from "next/document";
import React from "react";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head></Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () => {
return originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
};
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
If you're not using Server Side Rendering for that component, you may try importing the component, by turning off ssr:
import dynamic from 'next/dynamic';
const Component = dynamic(() =>
import('../components/path_to_component'), {
ssr: false,
});
Hope this helps!
Related
I have my context Provider in my AppContext.tsx file
import { useState, useEffect, useContext, createContext } from 'react';
interface AppContextProps {
children: React.ReactNode
}
const themes = {
dark: {
backgroundColor: 'black',
color: 'white'
},
light: {
backgroundColor: 'white',
color: 'black'
}
}
const initialState = {
dark: false,
theme: themes.light,
toggle: () => { console.log('toggle from initalState') }
}
const AppContext = createContext(initialState);
export function AppWrapper({ children }: AppContextProps) {
const [dark, setDark] = useState(false) // Default theme is light
// On mount, read the preferred theme from the persistence
useEffect(() => {
const isDark = localStorage.getItem('dark') === 'true'
setDark(isDark)
}, [dark])
// To toggle between dark and light modes
const toggle = () => {
console.log('toggle from AppWrapper')
const isDark = !dark
localStorage.setItem('dark', JSON.stringify(isDark))
setDark(isDark)
}
const theme = dark ? themes.dark : themes.light
return (
<AppContext.Provider value={{ theme, dark, toggle }}>
{children}
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
I insert Appwrapper into my _app.tsx file
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
import { AppWrapper } from '../contexts/AppContext';
import 'normalize.css/normalize.css';
import '../styles/globals.scss'
type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactElement) => ReactNode
}
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
const App = ({ Component, pageProps }: AppPropsWithLayout) => {
const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(
<AppWrapper>
<Component {...pageProps} />
</AppWrapper>
)
}
export default App
And I call and import the useAppContext hook to use it in my Header component
import styles from './header.module.scss'
import { useAppContext } from '../contexts/AppContext'
const Header = () => {
const { theme, toggle, dark } = useAppContext()
return (
<header className={styles.header}>
<nav className={styles.nav}>
<div onClick={toggle} className={styles.switchWrap}>
<label className={styles.switch}>
<input type="checkbox" />
<span className={styles.slider}></span>
</label>
</div>
</nav>
</header>
)
}
export default Header
However, my toggle function logs toggle from initalState instead of toggle from AppWrapper.
The following code below
const { theme, toggle, dark } = useAppContext()
gets data from initialState
const initialState = {
dark: false,
theme: themes.light,
toggle: () => { console.log('toggle from initalState') }
}
instead of what I pass into the value props shown below
<AppContext.Provider value={{ theme, dark, toggle }}>
{children}
</AppContext.Provider>
How do I correctly pass the value props data into my component instead of the data from initialState?
Because my app was using per-page layouts, I inserted AppWrapper in my layout file instead of _app.tsx and everything works
import Header from './header'
import Footer from './footer'
import { AppWrapper } from '../contexts/AppContext';
interface LayoutProps {
children: React.ReactNode
}
const Layout = ({ children }: LayoutProps) => {
return (
<AppWrapper>
<Header />
{children}
<Footer />
</AppWrapper>
)
}
export const getLayout = (page: LayoutProps) => <Layout>{page}</Layout>;
export default Layout
I'm stucking on a problem with React-Router and querySelector.
I have a Navbar component which contains all the CustomLink components for navigation and a line animation which listens to those components and displays animation according to the current active component.
// Navbar.tsx
import React, { useCallback, useEffect, useState, useRef } from "react";
import { Link, useLocation } from "react-router-dom";
import CustomLink from "./Link";
const Layout: React.FC = ({ children }) => {
const location = useLocation();
const navbarRef = useRef<HTMLDivElement>(null);
const [pos, setPos] = useState({ left: 0, width: 0 });
const handleActiveLine = useCallback((): void => {
if (navbarRef && navbarRef.current) {
const activeNavbarLink = navbarRef.current.querySelector<HTMLElement>(
".tdp-navbar__item.active"
);
console.log(activeNavbarLink);
if (activeNavbarLink) {
setPos({
left: activeNavbarLink.offsetLeft,
width: activeNavbarLink.offsetWidth,
});
}
}
}, []);
useEffect(() => {
handleActiveLine();
}, [location]);
return (
<>
<div className="tdp-navbar-content shadow">
<div ref={navbarRef} className="tdp-navbar">
<div className="tdp-navbar__left">
<p>Todo+</p>
<CustomLink to="/">About</CustomLink>
<CustomLink to="/login">Login</CustomLink>
</div>
<div className="tdp-navbar__right">
<button className="tdp-button tdp-button--primary tdp-button--border">
<div className="tdp-button__content">
<Link to="/register">Register</Link>
</div>
</button>
<button className="tdp-button tdp-button--primary tdp-button--default">
<div className="tdp-button__content">
<Link to="/login">Login</Link>
</div>
</button>
</div>
<div
className="tdp-navbar__line"
style={{ left: pos.left, width: pos.width }}
/>
</div>
</div>
<main className="page">{children}</main>
</>
);
};
export default Layout;
// CustomLink.tsx
import React, { useEffect, useState } from "react";
import { useLocation, useHistory } from "react-router-dom";
interface Props {
to: string;
}
const CustomLink: React.FC<Props> = ({ to, children }) => {
const location = useLocation();
const history = useHistory();
const [active, setActive] = useState(false);
useEffect(() => {
if (location.pathname === to) {
setActive(true);
} else {
setActive(false);
}
}, [location, to]);
return (
// eslint-disable-next-line react/button-has-type
<button
className={`tdp-navbar__item ${active ? "active" : ""}`}
onClick={(): void => {
history.push(to);
}}
>
{children}
</button>
);
};
export default CustomLink;
But it doesn't work as I want. So I opened Chrome Devtool and debugged, I realized that when I clicked on a CustomLink first, the querySelector() from Navbar would return null. But if I clicked on the same CustomLink multiple times, it would return properly, like the screenshot below:
Error from Chrome Console
How can I get the correct return from querySelector() from the first time? Thank you!
It's because handleActiveLine will trigger before setActive(true) of CustomLink.tsx
Add a callback in CustomLink.tsx:
const CustomLink: React.FC<Props> = ({ onActive }) => {
useEffect(() => {
if (active) {
onActive();
}
}, [active]);
}
In Navbar.tsx:
const Layout: React.FC = ({ children }) => {
function handleOnActive() {
// do your query selector here
}
// add onActive={handleOnActive} to each CustomLink
return <CustomLink onActive={handleOnActive} />
}
I recently started to use Next.JS on a new project and it's working fine but I can't figure out how to keep my layout state active on client side when I throw a 404 error after clicking on a wrong link.
Thanks to Adam's Wathan article, I'm able to share my state between different pages :
On home page
On "about" page
But if i click on "error", it will render the _error.tsx without preserving data i wrote in the input.
This page seems to render the whole app tree on server side, despite the fact I provide the same layout. Is there anyway to prefetch this page, just like a regular one and avoid to lose some information without using a solution like Redux ? I'm note quite familiar with it and it seems a bit too much in this case.
Here is my code:
pages/_error.tsx
import { getLayout } from "../components/layout/mainLayout"
import { withTranslation } from "../i18n";
import { FlexDirectionProperty } from "csstype";
const imgStyle = {
maxWidth: "100%",
maxHeight: "100%"
};
const figureStyle = {
height: "80vh",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column" as FlexDirectionProperty
};
const CustomError: any = ({ status, t }: any) => (
<>
<figure style={figureStyle}>
<figcaption>
<h1>Statut:{t('WELCOME')}</h1>
</figcaption>
<img
alt="Showing a properly cat according the status code"
src={`https://http.cat/${status}`}
style={imgStyle}
/>
</figure>
</>
)
CustomError.getLayout = getLayout;
CustomError.getInitialProps = async ({ res, err }: any) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : null
return {
statusCode: statusCode,
namespacesRequired: ["common"]
}
};
export default withTranslation('common')(CustomError)
components/layout/header.tsx
import Link from "next/link";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
import { withTranslation, i18n } from "../../i18n";
import { capitalize } from "../../helpers/utils";
import Modal from "react-bootstrap/Modal";
import { useState } from "react";
const loadStamp = +new Date();
const Header: any = ({ t }: any) => {
const [show, setShow] = useState(false);
const [active, setActive] = useState("home");
return (
<>
<Navbar fixed="top" bg="light">
<Nav className="mr-auto" activeKey={active}>
<Nav.Item>
<Link href="/">
<Navbar.Brand onClick={() => setActive("home")} href="/">Random Project</Navbar.Brand>
</Link>
</Nav.Item>
...
</Nav>
</Navbar>
</>);
};
export default withTranslation("common")(Header);
components/layout/mainLayout.tsx
import Header from "./header";
import "bootstrap/dist/css/bootstrap.min.css";
import "../../public/stylesheets/style.scss";
type LayoutProps = {
title?: string;
children: any;
};
const Layout: React.FunctionComponent<LayoutProps> = ({ children }) => (
<>
<Header />
<main role="main" className="main">
{children}
</main>
</>
);
export const getLayout: any = (page: any) => <Layout>{page}</Layout>
export default Layout
And my _app.tsx
import React from 'react'
import App from 'next/app'
import { appWithTranslation } from '../i18n'
class MyApp extends App<any> {
render() {
const { Component, pageProps } = this.props
const getLayout = Component.getLayout || ((page: any) => page)
return (
<>
{getLayout(
<Component {...pageProps}></Component>
)}
</>
)
}
}
export default appWithTranslation(MyApp)
can you please help me to change the React Material UI theme Dynamically .
https://imgur.com/S8zsRPQ
https://imgur.com/Ul8J40N
I have tried by changing the theme Properties on button click . The theme properties are getting changed as seen in the console . But the change is not reflecting on the theme .
Sandbox Code : https://codesandbox.io/s/30qwyk92kq
const themeChange = () => {
alert(theme);
theme.palette.type = "light";
console.log(theme);
};
ReactDOM.render(
<MuiThemeProvider theme={theme}>
<React.Fragment>
<CssBaseline />
<App changeTheme={themeChange} />
</React.Fragment>
</MuiThemeProvider>,
document.getElementById("app")
);
When I click the button the theme has to change to Dark color
I am using styledComponents, typescript and material-ui.
First I defined my themes:
// This is my dark theme: dark.ts
// I defined a light.ts too
import createMuiTheme from '#material-ui/core/styles/createMuiTheme';
export const darkTheme = createMuiTheme({
palette: {
type: 'dark', // Name of the theme
primary: {
main: '#152B38',
},
secondary: {
main: '#65C5C7',
},
contrastThreshold: 3,
tonalOffset: 0.2,
},
});
I defiend a themeProvider function and in this function I wrapped the material-ui's ThemeProvider in a React context to be able to change the theme easily:
import React, { useState } from 'react';
import {ThemeProvider} from "#material-ui/core/styles/";
import { lightTheme } from "./light";
import { darkTheme } from "./dark";
const getThemeByName = (theme: string) => {
return themeMap[theme];
}
const themeMap: { [key: string]: any } = {
lightTheme,
darkTheme
};
export const ThemeContext = React.createContext(getThemeByName('darkTheme'));
const ThemeProvider1: React.FC = (props) => {
// State to hold the selected theme name
const [themeName, _setThemeName] = useState('darkTheme');
// Retrieve the theme object by theme name
const theme = getThemeByName(themeName);
return (
<ThemeContext.Provider value={_setThemeName}>
<ThemeProvider theme={theme}>{props.children}</ThemeProvider>
</ThemeContext.Provider>
);
}
export default ThemeProvider1;
Now I can use it in my components like this:
import React from 'react';
import styled from 'styled-components';
import useTheme from "#material-ui/core/styles/useTheme";
const MyCardHeader = styled.div`
width: 100%;
height: 40px;
background-color: ${props => props.theme.bgColor};
color: ${props => props.theme.txtColor};
display: flex;
align-items:center;
justify-content: center;
`;
export default function CardHeader(props: { title: React.ReactNode; }) {
const theme = {
bgColor: useTheme().palette.primary.main,
txtColor: useTheme().palette.primary.contrastText
};
return (
<MyCardHeader theme={theme}>
{props.title}
</MyCardHeader>
);
}
For Changing between themes:
import React, {useContext} from 'react';
import { ThemeContext} from './themes/themeProvider';
export default function Header() {
// Get the setter function from context
const setThemeName = useContext(ThemeContext);
return (
<header>
<button onClick={() => setThemeName('lightTheme')}>
light
</button>
<button onClick={() => setThemeName('darkTheme')}>
dark
</button>
</header>
);
}
I'm using Material UI v4.
I tried something like Ashkan's answer, but it didn't work for me.
However, I found this in the documentation, and abstracting it to apply to a different piece of state, instead of user preference, worked for me. For your example, I'd probably make a context:
// context.js
import React, { useContext } from "react";
import { ThemeProvider, createTheme } from "#material-ui/core/styles";
import CssBaseline from "#material-ui/core/CssBaseline";
const CustomThemeContext = React.createContext();
// You can add more to these and move them to a separate file if you want.
const darkTheme = {
palette: {
type: "dark",
}
}
const lightTheme = {
palette: {
type: "light",
}
}
export function CustomThemeProvider({ children }) {
const [dark, setDark] = React.useState(false);
function toggleTheme() {
if (dark === true) {
setDark(false);
} else {
setDark(true);
}
}
const theme = React.useMemo(
() => {
if (dark === true) {
return createTheme(darkTheme);
}
return createTheme(lightTheme);
},
[dark],
);
return (
<CustomThemeContext.Provider value={toggleTheme}>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</CustomThemeContext.Provider>
);
}
export function useToggleTheme() {
const context = useContext(CustomThemeContext);
if (context === undefined) {
throw new Error("useCustomThemeContext must be used within an CustomThemeProvider");
}
return context;
}
Then wrap your app in that:
ReactDOM.render(
<CustomThemeProvider>
<App />
</CustomThemeProvider>,
document.getElementById("app")
);
And then access it in your app:
export default function App(){
const toggleTheme = useToggleTheme();
return (
<div>
<button onClick={toggleTheme}>Toggle the theme!!</button>
</div>
);
}
On a side note, in my app, I actually have a different theme in two sections of the app, based on whether the user is logged in or not. So I'm just doing this:
function App() {
const { authState } = useAuthContext();
const theme = React.useMemo(
() => {
if (authState.user) {
return createTheme(dashboardTheme);
}
return createTheme(loginTheme);
},
[authState.user],
);
return (
<ThemeProvider theme={theme}>
<TheRestOfTheApp />
</ThemeProvider>
}
It seems you can base the theme off any piece of state, or multiple pieces, by referencing them in useMemo and including them in the dependency array.
EDIT:
I just noticed that MUI v5 actually has something very similar in their docs.
In your code, theme type is changed. But the Page is not re-rendered with new theme.
I have changed code in index.js and App.js like following.
Try this approach. It works.
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(
<App/>,
document.getElementById("app")
);
App.js
import React from "react";
import CssBaseline from "#material-ui/core/CssBaseline";
import Typography from "#material-ui/core/Typography";
import { Button } from "#material-ui/core";
import { MuiThemeProvider, createMuiTheme } from "#material-ui/core/styles";
import blueGrey from "#material-ui/core/colors/blueGrey";
import lightGreen from "#material-ui/core/colors/lightGreen";
class App extends React.Component {
constructor(props){
super(props);
this.state = {
themeType : 'dark',
}
}
changeTheme(){
if (this.state.themeType == 'dark'){
this.setState({themeType:'light'});
} else {
this.setState({themeType:'dark'});
}
}
render() {
let theme = createMuiTheme({
palette: {
primary: {
light: lightGreen[300],
main: lightGreen[500],
dark: lightGreen[700]
},
secondary: {
light: blueGrey[300],
main: blueGrey[500],
dark: blueGrey[700]
},
type: this.state.themeType
}
});
return (
<MuiThemeProvider theme={theme}>
<CssBaseline />
<Typography>Hi there!</Typography>
<Button
variant="contained"
color="secondary"
onClick={()=>{this.changeTheme()}}
>
Change
</Button>
</MuiThemeProvider>
);
}
}
export default App;
I want to use a image to be put in as background image of login page in react-admin how can I do this ?
P.S: I'm using TypeScript
The Admin component have a loginPage prop. You can pass a custom component in that.
Here is an example, create your LoginPage component:
// LoginPage.js
import React from 'react';
import { Login, LoginForm } from 'react-admin';
import { withStyles } from '#material-ui/core/styles';
const styles = ({
main: { background: '#333' },
avatar: {
background: 'url(//cdn.example.com/background.jpg)',
backgroundRepeat: 'no-repeat',
backgroundSize: 'contain',
height: 80,
},
icon: { display: 'none' },
});
const CustomLoginForm = withStyles({
button: { background: '#F15922' },
})(LoginForm);
const CustomLoginPage = props => (
<Login
loginForm={<CustomLoginForm />}
{...props}
/>
);
export default withStyles(styles)(CustomLoginPage);
And use it in your Admin:
// App.js
import { Admin } from 'react-admin';
import LoginPage from './LoginPage';
export default const App = () => (
<Admin
loginPage={LoginPage}
{...props}
>
{resources}
</Admin>
);
More infos about this prop in the documentation: Admin.loginPage
For just a background image and nothing else:
https://marmelab.com/react-admin/Theming.html#using-a-custom-login-page you only need:
import { Admin, Login } from 'react-admin';
const MyLoginPage = () => <Login backgroundImage="/background.jpg" />;
const App = () => (
<Admin loginPage={MyLoginPage}>
</Admin>
);
Well, I find this solution in React Admin's issues
import React from 'react';
import { Login, LoginForm } from 'ra-ui-materialui';
import { withStyles } from '#material-ui/core/styles';
const styles = {
login: {
main: {
backgroundImage: 'url(https://source.unsplash.com/1600x900/?traffic,road)',
},
card: {
padding: '5px 0 15px 0',
},
},
form: {
button: {
height: '3em',
},
},
};
const MyLoginForm = withStyles(styles.form)(LoginForm);
const MyLogin = (props) => (
<Login loginForm={<MyLoginForm />} {...props} />
);
export default withStyles(styles.login)(MyLogin);
This is link click here