英文:
Page doesn't re-render when react context variable changes
问题
我正在使用React上下文来维护全局主题状态。
import React, { useState, createContext } from "react"
const ThemeContext = createContext(localStorage.getItem("theme") || "light")
export const ThemeStore = ({ children }) => {
const [theme, setTheme] = useState(localStorage.getItem("theme") || "light")
const handleThemeChange = theme => {
setTheme(theme)
localStorage.setItem("theme", theme === "dark" ? "dark" : "light")
}
return (
<ThemeContext.Provider value={{ theme, changeTheme: handleThemeChange }}>
{children}
</ThemeContext.Provider>
)
}
export default ThemeContext
我在我的Layout组件中提供了这个上下文,这样整个应用程序都可以访问存储。下面是我的Layout组件。
import React, { useContext } from "react"
import PropTypes from "prop-types"
import Header from "./header"
import "../styles/layout.css"
import ThemeContext, { ThemeStore } from "../contexts/ThemeContext"
import MobileBottomNav from "../components/mobileBottomNav"
// 背景颜色包装器
const Wrapper = ({ children }) => {
const theme = useContext(ThemeContext)
return (
<div className={`${theme.theme === "dark" ? "theme-dark" : "theme-light"}`}>
{children}
</div>
)
}
const Layout = ({ children, hideHeader, hideFooterNavBar }) => {
return (
<ThemeStore>
<Wrapper>
<div className="bg-bg-primary">
<Header hideHeader={hideHeader} />
<main>{children}</main>
<MobileBottomNav hideFooterNavBar={hideFooterNavBar} />
</div>
</Wrapper>
</ThemeStore>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
我的Header组件中有一个按钮,可以在浅色和深色主题之间切换。我的菜单组件等都会根据条件添加类名来更改它们的颜色。
但是在我的登录页面中,也包装在
这是我的登录页面。
import React, { useContext } from "react"
import SEO from "../components/seo"
import Layout from "../components/layout"
import ThemeContext from "../contexts/ThemeContext"
const SignInPage = () => {
const theme = useContext(ThemeContext)
// 这只是记录主题的初始值,但当我从标题组件更改主题时,这个值不会再次记录。组件不会重新渲染。
console.log(theme)
return (
<Layout hideFooterNavBar={true}>
<SEO title="Sign In" />
<div>
<div
style={{
paddingTop: "150px",
}}
className="container mx-auto"
>
<p
className={`${
theme.theme === "dark" ? "theme-dark" : "theme-light"
} text-text-primary mb-10`}
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In non
sapien blandit, aliquet lorem quis, consectetur tellus. Maecenas ac
nibh eu enim auctor volutpat. Mauris a lacus magna. Proin
sollicitudin mauris sit amet auctor feugiat. Nulla facilisi.
Pellentesque eget massa nec massa porta tristique in vitae ante.
Donec rutrum imperdiet urna.
</p>
</div>
</div>
</Layout>
)
}
export default SignInPage
如果您有任何其他问题或需要进一步帮助,请随时提出。
英文:
I am using React context to keep a global theme state.
import React, { useState, createContext } from "react"
const ThemeContext = createContext(localStorage.getItem("theme") || "light")
export const ThemeStore = ({ children }) => {
const [theme, setTheme] = useState(localStorage.getItem("theme") || "light")
const handleThemeChange = theme => {
setTheme(theme)
localStorage.setItem("theme", theme === "dark" ? "dark" : "light")
}
return (
<ThemeContext.Provider value={{ theme, changeTheme: handleThemeChange }}>
{children}
</ThemeContext.Provider>
)
}
export default ThemeContext
I am providing this context in my Layout Component so that my entire app can access the store. Below is my Layout component.
import React, { useContext } from "react"
import PropTypes from "prop-types"
import Header from "./header"
import "../styles/layout.css"
import ThemeContext, { ThemeStore } from "../contexts/ThemeContext"
import MobileBottomNav from "../components/mobileBottomNav"
// Wrapper for background color
const Wrapper = ({ children }) => {
const theme = useContext(ThemeContext)
return (
<div className={`${theme.theme === "dark" ? "theme-dark" : "theme-light"}`}>
{children}
</div>
)
}
const Layout = ({ children, hideHeader, hideFooterNavBar }) => {
return (
<ThemeStore>
<Wrapper>
<div className="bg-bg-primary">
<Header hideHeader={hideHeader} />
<main>{children}</main>
<MobileBottomNav hideFooterNavBar={hideFooterNavBar} />
</div>
</Wrapper>
</ThemeStore>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
I have a button in my Header component that changes the theme from light to dark and vice versa. And my menu component etc. all change their colors, as I've added conditional classnames.
But in my signin page, which is also wrapped around the <Layout> provider doesn't update when the state is changed. So if I change the theme from dark to light but it doesn't re-render the signup page, and hence the colors don't change. Why is it so? How do I make the page re-render when the theme changes so the new colors are applied.
Here is my signin page.
import React, { useContext } from "react"
import SEO from "../components/seo"
import Layout from "../components/layout"
import ThemeContext from "../contexts/ThemeContext"
const SignInPage = () => {
const theme = useContext(ThemeContext)
// This just logs the initial value of theme, but when I change
// the theme from the header component, this value doesn't log again.
// The component doesn't re-render.
console.log(theme)
return (
<Layout hideFooterNavBar={true}>
<SEO title="Sign In" />
<div>
<div
style={{
paddingTop: "150px",
}}
className="container mx-auto"
>
<p
className={`${
theme.theme === "dark" ? "theme-dark" : "theme-light"
} text-text-primary mb-10`}
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In non
sapien blandit, aliquet lorem quis, consectetur tellus. Maecenas ac
nibh eu enim auctor volutpat. Mauris a lacus magna. Proin
sollicitudin mauris sit amet auctor feugiat. Nulla facilisi.
Pellentesque eget massa nec massa porta tristique in vitae ante.
Donec rutrum imperdiet urna.
</p>
</div>
</div>
</Layout>
)
}
export default SignInPage
答案1
得分: 1
Your issue is that you are consuming ThemeContext
beneath where you're providing a value to ThemeContext.Provider
.
If you check the React devtools you should see that your tree looks something like this:
- SignInPage
- Layout
- ThemeStore
- ThemeContext.Provider
- Wrapper
- ThemeContext.Provider
- ThemeStore
- Layout
What you can do instead is consume the context in a component that is a child of Layout
or use a higher order component to inject the ThemeContext into SignInPage
:
const SignInPage = ({ theme }) => {
// ...
}
export default props => (
<ThemeContext.Consumer>
{theme =>
<SignInPage theme={theme} {...props} />
}
</ThemeContext.Consumer>
)
英文:
Your issue is that you are consuming ThemeContext
beneath where you're providing a value to ThemeContext.Provider
.
If you check the React devtools you should see that your tree looks something like this:
- SignInPage
- Layout
- ThemeStore
- ThemeContext.Provider
- Wrapper
- ThemeContext.Provider
- ThemeStore
- Layout
What you can do instead is consume the context in a component that is a child of Layout
or use a higher order component to inject the ThemeContext into SignInPage
:
const SignInPage = ({ theme }) => {
// ...
}
export default props => (
<ThemeContext.Consumer>
{theme =>
<SignInPage theme={theme} {...props} />
}
</ThemeContext.Consumer>
)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论