I decided to switch to styled-components and now I'm really struggling to make my dark/light theme work again. Before I used only css and relied upon css variables. I looked many tutorials and example for styled-components but the theme is always stored and changed on app/top component, while I preferably need it stored in the config component and rendered on another.
How could I do this without necessarily changing the structure ?
import * as sc from "./styles";
function App() {
return (
<>
<sc.globalStyles />
<sc.title>My app</sc.title>
<Configuration />
</>
);
}
function Configuration() {
const [config, setConfig] = useState(
retrieveFromStorage("configuration") ?? {
//other things
useDarkTheme: true,
}
)
useEffect(() => setToStorage(config, "configuration"), [config]);
const handleConfig = ({ target: { type, name, value, checked } }) => {
setConfig(prev => ({
...prev,
[name]: type === "select-one" ? value : checked,
}));
};
return (
<>
<sc.options>
<summary title="set your config">Options:</summary>
{/*other things*/}
</sc.options>
<Theme useDarkTheme={config.useDarkTheme} handleInput={handleConfig} />
</>
);
}
function Theme({ useDarkTheme, handleInput }) {
React.useEffect(
() => (useDarkTheme ? console.log("should be dark") : console.log("should be light")),
[useDarkTheme]
);
return (
<sc.theme>
☀️
<sc.toogleSwitch>
<sc.toogleTheme
type="checkbox"
name="useDarkTheme"
id="toogle"
defaultChecked={useDarkTheme}
onChange={handleInput}
/>
<sc.themeLabel htmlFor="toogle" />
</sc.toogleSwitch>
🌒
</sc.theme>
);
}
thanks!
Are you familiarized with React's Context API? From styled-components docs:
styled-components
has full theming support by exporting a<ThemeProvider>
wrapper component. This component provides a theme to all React components underneath itself via the context API.
Let's see how we could implement a ThemeProvider
with styled-components
.
We should also create a "custom hook" to ease access to our context throughout our app.
theme-context.jsx
import React from 'react'
export const ThemeContext = React.createContext({
// our theme object
theme: {},
// our color modes ('dark' || 'light')
colorMode: '',
// a method to toggle our theme from `dark` to `light` and vice-versa
setColorMode: () => null,
})
// export our custom hook for quick access to our context
export function useTheme() {
return React.useContext(ThemeContext)
}
styled-components
native <ThemeProvider>
to create our own ThemeProviderSince we'll need access to our themes, I'll add two very contrived theme objects (for the sake of simplicity) as well.
theme-provider.jsx
import React from 'react'
import { ThemeProvider as StyledProvider } from 'styled-components'
import { ThemeContext } from './theme-context'
// our theme objects
const lightTheme = { colorMode: 'light', bg: '#fff', text: '#000' }
const darkTheme = { colorMode: 'dark', bg: '#000', text: '#fff' }
// our iterable theme "store"
const myThemes = [lightTheme, darkTheme]
// our default color mode
const defaultColorMode = 'light'
const ThemeProvider = ({ children, ...props }) => {
// get fallback values from the parent ThemeProvider (if exists)
const {
theme: fallbackTheme,
colorMode: fallbackColorMode,
} = useTheme()
// initialize our state
const theme = props.theme ?? fallbackTheme
const [colorMode, setColorMode] = React.useState(
props.colorMode ?? fallbackColorMode ?? defaultColorMode,
)
// memoize the current theme
const resolvedTheme = React.useMemo(() => {
const theme = myThemes.find(t => t.colorMode === colorMode)
if (theme) return theme
return lightTheme
}, [theme, myThemes, colorMode])
// update our state if props change
React.useEffect(() => {
setColorMode(props.colorMode ?? fallbackColorMode ?? defaultColorMode)
}, [props.colorMode, fallbackColorMode])
return (
<ThemeContext.Provider
value={{
theme: resolvedTheme,
colorMode,
setColorMode,
}}
>
<StyledProvider theme={resolvedTheme}>{children}</StyledProvider>
</ThemeContext.Provider>
)
}
export default ThemeProvider
<App />
component within our ThemeProvider
app.jsx
import React from 'react'
import ThemeProvider from './theme-provider'
const App = () => {
const [themeType, setThemeType] = React.useState('light')
const switchThemes = () => {
setThemeType(last => (last === 'dark' ? 'light' : 'dark'))
}
return (
<ThemeProvider colorMode={themeType}>
<MySwitch onClick={switchThemes} />
</ThemeProvider>
)
}
And that's it. We should now be able to toggle our theme by clicking on MySwitch
. Hope that helps!
Let me know how it goes? Cheers