Murali V
Murali V

Reputation: 61

React Material UI Theme Change

can you please help me to change the React Material UI theme Dynamically .

https://i.sstatic.net/cdbgS.jpg https://i.sstatic.net/qRxPm.jpg

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

Upvotes: 3

Views: 15849

Answers (3)

Willow
Willow

Reputation: 1466

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.

Upvotes: 0

Ashkan
Ashkan

Reputation: 876

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>
    );
}

Upvotes: 4

LakshanSS
LakshanSS

Reputation: 125

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;

Upvotes: 0

Related Questions