【问题标题】:Change a MUI Theme from a nested component从嵌套组件更改 MUI 主题
【发布时间】:2021-08-07 20:01:27
【问题描述】:

我有以下组件结构:

App -> ThemeProvider -> MenuBar -> ThemeControls -> Switch and Autocomplete

我想使用开关更改为暗模式并使用自动完成更改原色。 为此,我添加了以下自定义挂钩。

import {createMuiTheme} from "@material-ui/core";
import {useState} from "react";

/**
 *  Hook to change the theme
 *
 * @returns {Theme, () => setDarkMode}
 */
const useThemeBuilder = () => {    
   
    const [myTheme, setMyTheme] = useState({})

    console.log('myTheme:', myTheme);
    let theme = createMuiTheme(myTheme);
    console.log('theme:' , theme);

    return [theme, setMyTheme]
}

export default useThemeBuilder;

钩子接受一个主题对象来用createMuiTheme覆盖默认对象。暴露了主题对象和改变钩子状态的回调!

以下是该对象的外观示例:

{
    "palette": {
        "type": "light",
        "primary": {
            "50": "#ffebee",
            "100": "#ffcdd2",
            "200": "#ef9a9a",
            "300": "#e57373",
            "400": "#ef5350",
            "500": "#f44336",
            "600": "#e53935",
            "700": "#d32f2f",
            "800": "#c62828",
            "900": "#b71c1c",
            "A100": "#ff8a80",
            "A200": "#ff5252",
            "A400": "#ff1744",
            "A700": "#d50000"
        }
    }
}

我现在的问题是 App.js 没有重新渲染并且思想没有注入新主题。

这是我的app.js

import './App.css';
import React from 'react';
import {BrowserRouter as Router, Route, Switch,} from "react-router-dom";
import {MyComponent} from "./components/MyComponent";
import {ThemeProvider, useTheme} from "@material-ui/core/styles";
import {MenuBar} from "./components/MenuBar";
import {MyButtons} from "./components/MyButtons";
import {Home} from "./components/Home";
import {MyForms} from "./components/MyForms";
import {MyHookedForm} from "./components/MyHookedForm";
import {MyCarousel} from "./components/MyCarousel";
import useThemeBuilder from "./components/theme/theme";
import {CssBaseline} from "@material-ui/core";


function App() {

    const [theme] = useThemeBuilder();
    console.log('theme from App:', theme)

    return (
        <Router>
            <ThemeProvider theme={theme}>
                <CssBaseline/>
                <MenuBar/>
                <Switch>
                    <Route path="/colors">
                        <MyComponent/>
                    </Route>
                    <Route path="/buttons">
                        <MyButtons/>
                    </Route>
                    <Route path="/forms">
                        <MyForms/>
                    </Route>
                    <Route path="/hook-forms">
                        <MyHookedForm/>
                    </Route>
                    <Route path="/carousel">
                        <MyCarousel/>
                    </Route>
                    <Route path="/">
                        <Home/>
                    </Route>
                </Switch>

            </ThemeProvider>
        </Router>
    );
}


export default App;

【问题讨论】:

  • 你在哪里调用 setMyTheme?
  • 您需要的是上下文。由于您使用的是 useState ,因此每个使用钩子的组件都有自己的主题和 setMyTheme 变量。您应该创建一个导出 setMyTheme 函数的上下文提供程序。您还可以在该上下文提供程序中包含 ThemeProvider

标签: reactjs material-ui


【解决方案1】:

这里是@Abdullah 建议的更改后的代码:

import './App.css';
import React, {createContext, useContext, useEffect, useState} from 'react';
import {BrowserRouter as Router, Route, Switch,} from "react-router-dom";
import {MyComponent} from "./components/MyComponent";
import {createMuiTheme, ThemeProvider} from "@material-ui/core/styles";
import {MenuBar} from "./components/MenuBar";
import {MyButtons} from "./components/MyButtons";
import {Home} from "./components/Home";
import {MyForms} from "./components/MyForms";
import {MyHookedForm} from "./components/MyHookedForm";
import {MyCarousel} from "./components/MyCarousel";
import {CssBaseline} from "@material-ui/core";

const ThemeContext = createContext({theme: {}});
export const useMyTheme = () => useContext(ThemeContext);

function App() {

    const [myTheme, setMyTheme] = useState();
    const [theme, setTheme] = useState(createMuiTheme({}));

    useEffect(()=> {
        console.log('create a theme in app with', myTheme);
        setTheme(createMuiTheme(myTheme));
    },[myTheme])

    return (
        <ThemeContext.Provider value={{theme, setMyTheme}}>
            <Router>
                <ThemeProvider theme={theme}>
                    <CssBaseline/>
                    <MenuBar/>
                    <Switch>
                        <Route path="/colors">
                            <MyComponent/>
                        </Route>
                        <Route path="/buttons">
                            <MyButtons/>
                        </Route>
                        <Route path="/forms">
                            <MyForms/>
                        </Route>
                        <Route path="/hook-forms">
                            <MyHookedForm/>
                        </Route>
                        <Route path="/carousel">
                            <MyCarousel/>
                        </Route>
                        <Route path="/">
                            <Home/>
                        </Route>
                    </Switch>

                </ThemeProvider>
            </Router>
        </ThemeContext.Provider>
    );
}


export default App;

组件ThemeControl.js动态改变主题:

import React, {useState, Fragment, useEffect} from "react";
import {Autocomplete} from "@material-ui/lab";
import {FormControlLabel, makeStyles, Switch, TextField, useTheme} from "@material-ui/core";
import useThemeBuilder from "./theme";
import {themeColors} from "./themeColors";
import {useMyTheme} from "../../App";

const useStyles = makeStyles((theme) => ({
    autoComplete: {
        marginRight: theme.spacing(2),
        width: '200px',
    }
}));

const valuesForAutocomplete = [
    {val: "red", text: "Red"},
    {val: "lime", text: "Lime"},
    {val: "amber", text: "Amber"},
];

export const ThemeControls = () => {

    const {setMyTheme} = useMyTheme();
    const theme = useTheme();
    const [darkMode, setDarkMode] = useState(theme.palette.type == 'dark' ? true : false);
    const [primaryColor, setPrimaryColor] = useState(valuesForAutocomplete[0]);
    const [primaryColorInput, setPrimaryColorInput] = useState('red');

    const classes = useStyles();

    useEffect(() => {
        console.log('Primary-Object', theme.palette.primary);
        console.log('Primary Color:', primaryColor);
        setMyTheme({
            palette: {
                type: darkMode ? "dark" : "light",
                primary: primaryColor ? themeColors[primaryColor.val] : themeColors.blue,
            },
        })
    },[primaryColor,darkMode])


    const switchMode = () => {
        console.log('switch mode')
        setDarkMode(!darkMode);
    }

    const switchPrimary = (value) => {
        console.log('switch primary color');
        setPrimaryColorInput(value);
    }

    return (
        <Fragment>
            <Autocomplete
                className={classes.autoComplete}
                options={valuesForAutocomplete}
                getOptionLabel={option => option.text}
                value={primaryColor}
                onChange={(event, newValue) => setPrimaryColor(newValue)}
                //onInputChange={(event, newValue) => setPrimaryColorInput(newValue)}
                onInputChange={(event, newValue) => {
                    switchPrimary(newValue)
                }}
                inputValue={primaryColorInput}
                renderInput={(params) =>
                    <TextField
                        {...params}
                        variant='outlined'
                        label='Primary Color'
                        InputLabelProps={{shrink: true}}
                    />}
            />
            <FormControlLabel control={
                <Switch
                    checked={darkMode}
                    onChange={switchMode}
                />
            } label="Dark Mode"/>
        </Fragment>
    )
}

【讨论】:

    【解决方案2】:

    您需要的是上下文。由于您使用的是 useState ,因此每个使用钩子的组件都有自己的主题和 setMyTheme 变量。您应该创建一个导出 setMyTheme 函数的上下文提供程序。

    你可以这样做

    import './App.css';
    import React from 'react';
    import {BrowserRouter as Router, Route, Switch,} from "react-router-dom";
    import {MyComponent} from "./components/MyComponent";
    import {ThemeProvider, useTheme} from "@material-ui/core/styles";
    import {MenuBar} from "./components/MenuBar";
    import {MyButtons} from "./components/MyButtons";
    import {Home} from "./components/Home";
    import {MyForms} from "./components/MyForms";
    import {MyHookedForm} from "./components/MyHookedForm";
    import {MyCarousel} from "./components/MyCarousel";
    import useThemeBuilder from "./components/theme/theme";
    import {CssBaseline} from "@material-ui/core";
    
    const ThemeContext = React.createContext({theme:{}});
    export const useMyTheme = ()=>React.useContext(ThemeContext);
    
    function App() {
    
        const [theme,setMyTheme] = useState({});
        console.log('theme from App:', theme)
    
        return (
    <ThemeContext.Provider value={{theme,setMyTheme}}>
            <Router>
                <ThemeProvider theme={theme}>
                    <CssBaseline/>
                    <MenuBar/>
                    <Switch>
                        <Route path="/colors">
                            <MyComponent/>
                        </Route>
                        <Route path="/buttons">
                            <MyButtons/>
                        </Route>
                        <Route path="/forms">
                            <MyForms/>
                        </Route>
                        <Route path="/hook-forms">
                            <MyHookedForm/>
                        </Route>
                        <Route path="/carousel">
                            <MyCarousel/>
                        </Route>
                        <Route path="/">
                            <Home/>
                        </Route>
                    </Switch>
    
                </ThemeProvider>
            </Router>
    <ThemeContext.Provider>
        );
    }
    
    
    export default App;
    

    在你想用的组件里面可以做

    const {theme,setMyTheme} = useMyTheme();
    

    【讨论】:

    • 那行得通-谢谢@Abdullah。由于 myTheme 只是要覆盖的部分 - 传递给 createMuiTheme 我不得不稍微调整一下。我在另一个答案中分享了代码!
    猜你喜欢
    • 2022-08-09
    • 2019-02-22
    • 2021-04-25
    • 2021-11-23
    • 1970-01-01
    • 2019-11-26
    • 1970-01-01
    • 2018-05-28
    • 2018-12-24
    相关资源
    最近更新 更多