React Native custom theme selector
Herbie
Posted on November 1, 2021
Theming a mobile app can be a tricky thing to do, and quite daunting if you're new to the react native and javascript ecosystem. But I've tried to make this post clear and straightforward, so you shouldn't have any issues (and if you do, leave them in the comments).
Step 1 - Defining your colors
Create a file and add all of your colors to it (I added it to ./src/lib/constants.ts
[see a live example here])
You don't have to stick with light
and dark
, you can add custom themes such as sepia
or navy
.
Step 2 - Create functions to communicate with the native storage API
You need to create two functions to communicate with the native storage provider. This serves two purposes
- It persists the theme on the local device
- Allows local storage access for web, iOS and Android
You will need to this package to manage local storage in React Native.
The functions will look something like this:
const os = Platform.OS
const webStorage = window.localStorage
const appStorage = AsyncStorage
const getItem = async (key: string) => {
if (key) {
return os === 'web'
? webStorage.getItem(key)
: await appStorage.getItem(key)
}
return null
}
const setItem = async (key: string, payload: string) => {
if (key && payload) {
return os === 'web'
? webStorage.setItem(key, payload)
: await appStorage.setItem(key, payload)
}
return null
}
I saved this file here: ./src/lib/storage.ts
Step 3 - Creating a theme context
Due to the theme data being shared only with components, we can use React's Context API. This will provide a globally accessible state that you can use within all of your app. The context will hold two variables:
theme: 'light' | 'dark'
: you need this to know what theme is selected
setTheme: React.Dispatch<React.SetStateAction<'light' | 'dark'>>
: this is to change the theme
The context will look something like this:
import { useColorScheme } from 'react-native'
import { getItem, setItem } from '../lib/storage'
export type ThemeOptions = 'light' | 'dark'
export interface ThemeContextInterface {
theme: ThemeOptions
setTheme: Dispatch<SetStateAction<ThemeOptions>>
}
export const ThemeContext = React.createContext<ThemeContextInterface | null>(
null
)
const ThemeProvider: React.FC<{}> = ({ children }) => {
// default theme to the system
const scheme = useColorScheme()
const [theme, setTheme] = useState<ThemeOptions>(scheme ?? 'dark')
// fetch locally cached theme
useEffect(() => {
const fetchTheme = async () => {
const localTheme = await getItem('theme')
return localTheme
}
fetchTheme().then((localTheme) => {
if (localTheme === 'dark' || localTheme === 'light') {
setTheme(localTheme)
}
})
}, [])
// set new theme to local storage
useEffect(() => {
setItem('theme', theme)
}, [theme])
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
Step 4 - Creating the hook
The hook is the middleman between the state and the UI. Its main purpose is to deliver the correct colors based on the Theme Context.
The useTheme
hook looks like this:
// import ThemeContext and your colors
export interface Theme {
background: string
backgroundVariant: string
text: string
variant: string
secondary: string
secondaryVariant: string
accent: string
success: string
warning: string
error: string
}
const lightTheme: Theme = {
background: LIGHT_THEME_BACKGROUND,
backgroundVariant: LIGHT_THEME_BACKGROUND_VARIANT,
text: LIGHT_THEME_TEXT,
variant: LIGHT_THEME_VARIANT,
secondary: LIGHT_THEME_SECONDARY,
secondaryVariant: LIGHT_THEME_SECONDARY_VARIANT,
accent: SEMERU_BRAND,
success: SUCCESS,
warning: WARNING,
error: ERROR,
}
const darkTheme: Theme = {
background: DARK_THEME_BACKGROUND,
backgroundVariant: DARK_THEME_BACKGROUND_VARIANT,
text: DARK_THEME_TEXT,
variant: DARK_THEME_VARIANT,
secondary: DARK_THEME_SECONDARY,
secondaryVariant: DARK_THEME_SECONDARY_VARIANT,
accent: SEMERU_BRAND,
success: SUCCESS,
warning: WARNING,
error: ERROR,
}
interface UseThemeHook {
theme: Theme
setTheme: Dispatch<SetStateAction<'light' | 'dark'>>
}
const useTheme = (): UseThemeHook => {
const { theme, setTheme } = useContext(ThemeContext)!
if (theme === 'dark') {
return {
theme: darkTheme,
setTheme,
}
}
return {
theme: lightTheme,
setTheme,
}
}
Step 5 - Enjoy!
All you need to do now is to use it in your UI. Import useTheme
and use it as you please!
An example of consuming the colors:
const App: React.FC = () => {
const { theme } = useTheme()
return (
<View style={{ background: theme.background }}>
...
</View>
)
}
An example of mutating the colors:
const App: React.FC = () => {
const { setTheme } = useTheme()
return (
<Pressable onPress={() => setTheme(prev => prev === 'light' ? 'dark' : 'light')}>
<Text>Change theme</Text>
</Pressable>
)
}
And that's it!
There is however a step 6, and that simply involves liking this post and sharing on Twitter. I would really appreciate it :)
Posted on November 1, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.