Theming React native applications with Styled components

Theming React native applications with Styled components

Theming has become an essential part of mobile applications. Giving users the ability to switch between dark and light mode goes a long way in improving user experience and accessibility. In this article, we will be looking at how to implement dark mode in a mobile application using React native and Styled Components.

Styled components is CSS in JS solution for styling React and React native applications. This means you can write CSS in your JavaScript code rather than keeping styles in a separate stylesheet. Some of the advantages of this method are:

  • Eliminates classname bugs: As developers, sometimes we might misspell a class name or run into specificity issues due to duplicate styles. Styled components solve this by generating unique class names for your styles.
  • Automatic critical styles: Another disadvantage of traditional CSS is loading the entire stylesheet for every page opened, and component rendered even when some of the styles aren’t applied to elements in the page. Styled components only inject styles of components rendered which helps in improving performance
  • Simple dynamic styling: You can adapt the style of a component based on the props of a component without having to create separate styles.

Getting Started

We will be building a demo blog app with the Expo framework to show how we can use Styled components in a React native application. To get started, run the code below:

npm install -global expo-cli

The above command installs the [expo-cli](https://docs.expo.dev/workflow/expo-cli/), a tool used in bootstrapping react native applications with Expo.

After installation, run expo init theming-mobile to create a react native application theming-mobile. An interactive prompt will show up to select a template, choose blank and press the enter/return key to continue the installation

January 16, 2022 9:23:47 pm - Screenshot

Run the code below after the installation to install the necessary libraries we will need for our demo blog app.

expo install @react-native-async-storage/async-storage expo-linking

Once the process is complete, you’ve successfully bootstrapped an expo react-native application. To run the application, navigate to the project directory in a terminal and run the code expo start to start the expo development server. You will be presented with an interactive menu similar to the screenshot below

February 28, 2022 3:18:34 pm - Screenshot

The Expo dev server allows you to test your application locally while in development. You will need an emulator or the Expo Go App to run the app. The installation of an emulator isn’t covered in this article. Nevertheless, you can check here on how to install an emulator on your computer or the Expo Go app on your mobile device.

Assuming you have an emulator installed, press the relevant key that applies to the emulator, and it’ll run the app on the emulator.

Integrating Styled Components

We will be creating a Blog app to illustrate how to use styled-components. As earlier explained, Styled-components is used in styling React native applications. To get started with styled-components, run the code below at the root of your project to install the styled-components library

expo install styled-components

Next, create a style.js file in the root of your project directory and paste the code below.

import styled from 'styled-components/native';
import Constants from 'expo-constants';

export const Container = styled.SafeAreaView`
    background-color: #fff;
    flex: 1;
    align-items: center;
    justify-content: center;
    padding: 20px;
    padding-top: ${Constants.statusBarHeight + 'px'};
`;
export const Header = styled.View`
    display: flex;
    width: 100%;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    padding: 20px;
`;
export const ThemeButton = styled.Pressable`
    padding: 10px;
    border: 1px solid #000;
`;
export const ThemeButtonText = styled.Text`
    font-size: 16px;
    color: #000;
`;
export const TitleText = styled.Text`
    font-weight: 600;
    font-size: ${(props) => props.fontSize || '18px'};
    color: #000;
`;
export const PostContainer = styled.View`
    padding: 10px 20px;
    width: 100%;
`;
export const PostText = styled.Text`
    color: #73737d;
    font-size: 16px;
    padding: 10px 0 0;
    font-weight: ${(props) => props.fontWeight || '400'};
`;

We are using style.js to house all our styled components to be reused in other components. Styled components work by wrapping a react native component with custom styles in template literals.

export const Container = styled.SafeAreaView`
    background-color: #fff;
    flex: 1;
    align-items: center;
    justify-content: center;
    padding: 20px;
    padding-top: ${Constants.statusBarHeight + 'px'};
`;

In the example above, we create a styled component Container that wraps the react-native SafeAreaView component. We can also pass the components' props and other valid JavaScript code as the styles are wrapped in template literals as you can see in the following example:

export const PostText = styled.Text`
    color: #73737d;
    font-size: 16px;
    padding: 10px 0 0;
    font-weight: ${(props) => props.fontWeight || '400'};
`;

We can complete the application now that we’ve created our styled components. Create a data.js file in the application directory and paste the code below

export default [
    {
        id: 1,
        body: 'Top 3 design patterns used to create React.JS components that you should know about',
        title: '3 React Component Design Patterns You Should Know About',
        date: '11th Jan 2022',
        url: 'https://blog.openreplay.com/3-react-component-design-patterns-you-should-know-about',
    },
    {
        id: 2,
        body: 'Generate Github issues directly from the OpenReplay interface',
        title: 'Integrating OpenReplay with GitHub',
        date: '5th Nov 2021',
        url: 'https://blog.openreplay.com/integrating-openreplay-with-github',
    },
    {
        id: 3,
        body: 'Learn how to create JIRA tickets directly from an OpenReplay session',
        title: 'Integrating OpenReplay with Jira',
        date: '24th Sept 2021',
        url: 'https://blog.openreplay.com/integrating-openreplay-with-slack-in-a-web-application',
    },
];

This file will hold the mock blog data we will be using in our application. Create another file called PostItem.js file in your project directory and insert the code below

import * as Linking from 'expo-linking';
import { PostContainer, TitleText, PostText } from './style';
const PostItem = ({ item }) => {
    return (
        <PostContainer>
            <TitleText onPress={() => Linking.openURL(item.url)}>{item.title}</TitleText>
            <PostText>{item.body}...</PostText>
            <PostText fontWeight='600'>{item.date}</PostText>
        </PostContainer>
    );
};
export default PostItem;

The PostItem component will display a single blog post item that comprises the Blog title, body, and date the blog article was published. As you can notice, we aren’t using any React native component here directly but the styled-components we created.

Now open the App.js file and replace the code in the file with the code below

import { useState, useEffect } from 'react';
import { StatusBar } from 'expo-status-bar';
import { FlatList } from 'react-native';
import PostItem from './PostItem';
import data from './data';
import { Container, Header, ThemeButton, ThemeButtonText, TitleText } from './style';
export default function App() {
    return (
          <Container>
              <Header>
                  <TitleText fontSize='24px'>Blog</TitleText>
                  <ThemeButton>
                      <ThemeButtonText onPress={() => null}>
                          Dark Mode
                      </ThemeButtonText>
                  </ThemeButton>
              </Header>
              <FlatList data={data} renderItem={PostItem} keyExtractor={(item) => item.id} />
              <StatusBar style='auto' />
          </Container>
    );
}

Like what was done in the PostItem.js component, we use our styled components to replace the default components. We also introduced a FlatList component to render the mock data from the data.js file in the PostItem component.

Save and reload your emulator. You should get something similar to the screenshot below.

Open Source Session Replay

Debugging a web application in production may be challenging and time-consuming. OpenReplay is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue. It’s like having your browser’s inspector open while looking over your user’s shoulder. OpenReplay is the only open-source alternative currently available.

image.png

Happy debugging, for modern frontend teams - Start monitoring your web app for free.

Theming

Now that we have our application up and running, the next step is applying the themes. We already have a default light mode theme. We will implement a dark mode theme in this section. Create a theme.js file and paste the code below

const darkTheme = {
    PRIMARY_COLOR: '#000',
    SECONDARY_COLOR: '#73737d',
    TITLE_COLOR: '#fff',
    BACKGROUND_COLOR: '#111216',
    BUTTON_COLOR: '#fff',
};
const lightTheme = {
    PRIMARY_COLOR: '#fff',
    SECONDARY_COLOR: '#73737d',
    TITLE_COLOR: '#000',
    BACKGROUND_COLOR: '#fff',
    BUTTON_COLOR: '#000',
};
export { lightTheme, darkTheme };

This file contains the theme colors we will be using in the application. Now go back to the style.js code and replace the code with the code below

import styled from 'styled-components/native';
import Constants from 'expo-constants';
export const Container = styled.SafeAreaView`
    background-color: ${(props) => props.theme['PRIMARY_COLOR']};
    flex: 1;
    align-items: center;
    justify-content: center;
    padding: 20px;
    padding-top: ${Constants.statusBarHeight + 'px'};
`;
export const Header = styled.View`
    display: flex;
    width: 100%;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    padding: 20px;
`;
export const ThemeButton = styled.Pressable`
    padding: 10px;
    border: 1px solid ${(props) => props.theme['BUTTON_COLOR']};
`;
export const ThemeButtonText = styled.Text`
    font-size: 16px;
    color: ${(props) => props.theme['BUTTON_COLOR']};
`;
export const TitleText = styled.Text`
    font-weight: 600;
    font-size: ${(props) => props.fontSize || '18px'};
    color: ${(props) => props.theme['TITLE_COLOR']};
`;
export const PostContainer = styled.View`
    padding: 10px 20px;
    width: 100%;
`;
export const PostText = styled.Text`
    color: ${(props) => props.theme['SECONDARY_COLOR']};
    font-size: 16px;
    padding: 10px 0 0;
    font-weight: ${(props) => props.fontWeight || '400'};
`;

We’ve replaced the colours with props that will be passed down depending on the theme selected, so if a user selects dark mode, we’ll apply the colours from the darkMode theme in the theme.js folder to the styled-components.

Now go to the App.js file and paste the code below.

/** Previous code **/
import { ThemeProvider } from 'styled-components/native';
import { darkTheme, lightTheme } from './theme';

export default function App() {
    const [theme, setTheme] = useState('light');
    const toggleTheme = async () => {
        const themeValue = theme === 'dark' ? 'light' : 'dark';
        setTheme(themeValue); 
    };
    return (
        <ThemeProvider theme={theme === 'dark' ? darkTheme : lightTheme}>
            <Container>
                <Header>
                    <TitleText fontSize='24px'>Blog</TitleText>
                    <ThemeButton>
                        <ThemeButtonText onPress={() => toggleTheme()}>
                            {theme === 'dark' ? 'Light' : 'Dark'} Mode
                        </ThemeButtonText>
                    </ThemeButton>
                </Header>
                <FlatList data={data} renderItem={PostItem} keyExtractor={(item) => item.id} />
                <StatusBar style='auto' />
            </Container>
        </ThemeProvider>
    );
}

Styled components have theming support by default which is available with the ThemeProvider component based on React context. The ThemeProvider component allows us to pass data through the component tree without drilling props down through every component level.

<ThemeProvider theme={theme === 'dark' ? darkTheme : lightTheme}>

We pass the theme value down, which returns a theme object that depends on the state, so when a user toggles the theme, we update the theme state with the correct theme value and pass down the colours of the chosen theme to the styled-components. Reload the app, and now when you click the Dark mode button, it should switch the theme

Persisting the theme

We have now successfully created two themes for the application: light mode and dark mode, which is the aim of this article, but there is a small problem. If you reload the app, you’ll notice the theme resets back to light mode even after switching to dark mode.

To fix that, we will be using a React native library called Async Storage which provides a persistent storage that we can use to store string data. The idea is to store the current theme so that we can fetch the stored theme when the app is loaded and apply it to the application.

Go to your App.js file and insert the code below:

/** Previous code **/
import AsyncStorage from '@react-native-async-storage/async-storage';

export default function App() {
    const [theme, setTheme] = useState('light');
    useEffect(() => {
        getTheme();
    }, []);
    const getTheme = async () => {
        try {
            const themeValue = await AsyncStorage.getItem('@theme');
            if (themeValue) setTheme(themeValue);
        } catch (error) {
            console.log(error);
        }
    };
    const toggleTheme = async () => {
        const themeValue = theme === 'dark' ? 'light' : 'dark';
        try {
            await AsyncStorage.setItem('@theme', themeValue);
            setTheme(themeValue);
        } catch (error) {
            console.log(error);
        }
    };
    return(
      /** Previous code **/
    )
}

There are two functions to note in the code above the getTheme and toggleTheme functions. When the app loads for the first time, we check if there is an existing theme in the Async storage. If there is, we replace the default theme in the state with the existing one. If a user toggles the theme, we store the current theme in the Async storage and the theme state.

Now refresh your app again. You'll notice even when you quit and open the app again. It still uses the last saved theme.

Conclusion

At this point, you should be able to implement theming in your mobile applications. You could do so much more with Styled components, such as creating a design system and extending the styled-components. Please endeavour to check the Styled component docs. You can also try extending the app we created to include other themes. I’d love to see what you come up with. The complete code for this article is available here.