React Native custom theme selector

herbie
3 min readNov 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 :)

Originally published at https://dev.to on November 1, 2021.

--

--

herbie

student 👨‍🎓, web developer 👍 and cat lover 🐱