Adding support for dark and light themes in mobile applications can enhance the user experience by a manifold. From YouTube to Whatsapp, Instagram, Telegram or to Slack, many famous apps have extended the dark mode support to their platforms. And even if you prefer to remain in the light, one can easily leverage the dark mode support for various users in a few easy steps. Since the YouTube app looks a lot better in dark mode, let us implement dark mode by building a very basic clone of the app wherein we will mainly focus on toggling between dark and light theme.

The basic UI skeleton for the app, before we move ahead would look like as follows:

Light Mode

We have a custom header with the YouTube logo and some other icons, one of which (the theme toggling icon) will do our magic. A simple bottom tag navigator is also present similar to the app, with the active tab highlighted with red colour. You can find the UI starter code required for this project here

A dark mode version of the app would look like :

Implementing Dark Mode in React Native

Notice how the icon color changes to white and the active tab is also shown in white in contrast to red as in the light mode.

We can implement dark mode via a couple of options such as react-navigation, styled-components or the react-native-appearance. In this particular app we will be using Themes under React Navigation. You can find the documentation here.

We are provided with DefaultTheme and DarkTheme built in to React Navigation as well as an option of customizing these themes. In your App.js or wherever the entry point of your app is import the following:

 

import {DefaultTheme,DarkTheme} from '@react-navigation/native'

We can wrap the entry point of the app with the themes:

<NavigationContainer theme={DarkTheme}>
      <Stack.Navigator headerMode="none">
      <Stack.Screen name="home" component={Home} />
      </Stack.Navigator>
</NavigationContainer>

However, this alone will have no effect on our custom components like the header or the bottom navigator. We can customise them by changing the theme prop dynamically.

We customize both our themes: DarkTheme and the DefaultTheme as new JS objects in our App.js. In the DarkTheme we would like the icons and active icon to be white in color and the header background color to be gray. Similarily in the DefaultTheme we would like the icons to be black and the active icon to be red. The header background would be white. Thus we add these colours to the colours property for both of our themes.

const myDarkTheme={
  ...DarkTheme,
  colors:{
    ...DarkTheme.colors,
    headerColor:"#404040",
    iconColor:"white",
    activeTabColor:"white"
  }
}

const myDefaultTheme={
  ...DefaultTheme,
  colors:{
    ...DefaultTheme.colors,
    headerColor:"white",
    iconColor:"black",
    activeTabColor:"red"
  }
}

To use our customized themes in components rendered inside the navigation container (such as other screens like Search, Explore, Notifications or your custom header), we can use the hook : useTheme. Import the hook in your component as follows:

import {useTheme} from '@react-navigation/native'

The hook returns the theme object, whose property can now be accessed and used in our components.

So in our custom header get the theme object from the hook as follows:

export default function Header(){
    const {colors}=useTheme()
    /* your logic goes here */
}

Now we defined header color and icon colors in our customized themes in App.js. These properties can be accessed as colors.headerColor and colors.iconColor as follows:

export default function Header(){
    const {colors}=useTheme()
    return (
      <View style={[styles.header,{backgroundColor:colors.headerColor}]}>
        <View style={{flexDirection:'row',margin:5}}>
            <Entypo
            name="youtube"
            size={32}
            color="red"
            style={{marginLeft:20}}/>
            <Text style={[styles.headerText, 
              {color:colors.iconColor}]}>YouTube</Text> 
        /* Rest of the icons go here */
        </View>
}

The view with the header style has been given a background color as specified by the theme. The logo text “YouTube” too will have a font color as specified by the theme. For the other icons in our header, a similar process can be followed where the icons will have a color specified by colors.iconColor

We can also customize the icon colors and text colors in the bottom tab navigator.

<Tabs.Navigator
    screenOptions={({ route }) => ({ 

      //accessing the icon colors from tabBarOptions

      tabBarIcon: ({ color }) => {
        let iconName;

        //determining which icon to be used for each tab

        if (route.name === 'Home') {
          iconName = 'home';
        } else if (route.name === 'Explore') {
          iconName = 'explore';
        }else if(route.name === 'Subscriptions'){
          iconName = 'subscriptions'
        }
        else if(route.name === 'Notifications'){
          iconName = 'notifications'
        }
        else if(route.name === 'Library'){
          iconName = 'video-library'
        }
        
        //returning icons for each tab with the specified color

        return <MaterialIcons name={iconName} size={20} 
          color={color} />;
      },
    })}
    tabBarOptions={{

      //specifying the active tab color from our colors object returned from useTheme hook

      activeTintColor:colors.activeTabColor,
      inactiveTintColor: 'gray',
    }}>

And that’s it! Using a similar process you can customize other screens in your app as well. For example, you might want to change the text color as white when in dark mode and to black when in light mode. And it will be exactly similar to what we have done till now.

Now the only thing left it to toggle between the modes. Right now whatever theme the NavigationContainer is using, the corresponding customized theme is being used across the app. From our original UI mockup we have a button in the header (our theme icon) which will help us toggle between the themes. Since we need to use this theme value across various screens in the app we will have to make use of Redux.

Create a folder for our reducers in the src directory, inside which create a reducer file themeReducer.js and declare as follows:

const initialState=false
export const themeReducer=(state=initialState,action)=>{
    switch(action.type){
        case 'toggle_theme':
            return action.payload
        default:
            return state
    }   
}

Let’s use this reducer by creating a store folder store in src directory, under which create a file Index.js

import { createStore, combineReducers } from "redux";
import { themeReducer } from "../reducers/themeReducer";
const store=createStore(
    combineReducers({
          myDarkMode:themeReducer

    })

)
export default store

This is the central state of all our data.

Use this in App.js as follows:

import store from './src/store'
/* all your themes and logic goes here */

export default ()=>{
  return(
      
     // make the redux store available to the Navigation Component

     <Provider store={store}>
     <Navigation/>
     </Provider>
  )
}

export function Navigation(){

  //useSelector is the hook to access redux store's state, access whether dark mode is enabled or not

  let currentTheme=useSelector(state=>{
    return state.myDarkMode
  })
  return(
    <Provider store={store}>

    // if the current theme is enabled, use the customized dark theme else the default theme

    <NavigationContainer theme={currentTheme?myDarkTheme:myDefaultTheme}>
      <Stack.Navigator headerMode="none">
      <Stack.Screen name="home" component={Home} />
      </Stack.Navigator>
    </NavigationContainer>
    </Provider>
  )
}

That is how we access the current theme and share it across various screens. Now the last thing left is to modify the redux state i.e. enable toggling.

For this, inside the header.js file, use the hooks useDispatch and useSelector as follows:

import { useSelector, useDispatch } from 'react-redux';

export default function Header(){
// accessing the current theme
const dispatch=useDispatch() const currentTheme=useSelector(state=>{ return state.myDarkMode }) /* your logic goes here */
// the icon which has to toggle the theme, in the onPress handler call the toggle_theme action type and the payload to be the opposite of the current theme i.e. if the current theme is dark mode (value true) make it false ( which is default theme).
<MaterialCommunityIcons name="theme-light-dark" size={24} color={mycolor} onPress= {()=>dispatch({type:'add_theme',payload:!currentTheme})}/>

And that’t it! We have successfully implemented the support of toggling between dark and light modes in our react native app!

Find the complete code here.

Apart from React Navigation, here’s another read for implementing dark mode using react-native-appearance.

We will be coming up with more such React Native concepts. Stay tuned!

 


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *