Skip to main content

React Native Context API

Victoria Harbour, Hongkong

Github Repository

Setup

npm install -g expo-cli
expo init react-native-contextapi

Choose the blank template and run the client:

cd react-native-contextapi
npm install styled-components
npm start

I can test code by scanning the QR Code using the Expo Go app under Android or by pressing the w key to activate a web preview.

error:0308010C:digital envelope routines::unsupported: Newer versions of Node.js use OpenSSLv3 that brought some breaking changes. Can be fixed (>=Nodejs v17) by adding the following option to the start script inside your package.json -> "start": "export NODE_OPTIONS=--openssl-legacy-provider && expo start"

Theming with Context

Creating a context to provide a Dark / Light theme to an ReactNative App. Add a folder context to the src directory of your app and create a file ThemeContext.jsx:

import React, {createContext, Component} from 'react'

export const ThemeContext = createContext()

class ThemeContextProvider extends Component {
    state = {
        isDarkTheme: true,
        lightTheme: {
            color: 'purple',
            backgroundColor: 'snow'
        },
        darkTheme: {
            color: 'plum',
            backgroundColor: 'purple'
        }
    }

    render() {
        return (
            <ThemeContext.Provider value={{ ...this.state }}>
                {this.props.children}
            </ThemeContext.Provider>
        )
    }
 }


export default ThemeContextProvider

And inside your apps entry point, e.g. App.jsx wrap your code inside the theme provider:

import ThemeContextProvider from './src/context/ThemeContext'

const App = () => {
  return (
    <RootView>
      <ThemeContextProvider>
        <Navbar />
        <TodoList />
      </ThemeContextProvider>
    </RootView>
  );
}

export default App

Accessing Context

Static

Now both the apps navigation bar and todo list are children of the context provider. Which means that we have access to the state it is providing by calling this.context :

import React from 'react'

import { TodoBackground, TodoItem } from './_styles'
import { ThemeContext } from '../context/ThemeContext'

class ToDoList extends React.Component {
    static contextType = ThemeContext
    render() {

        const { isDarkTheme, darkTheme, lightTheme } = this.context
        // if isDarkTheme is true return dark state / else light
        const theme = isDarkTheme ? darkTheme : lightTheme

        return (
            <TodoBackground style={theme}>
                <TodoItem>One thing to do</TodoItem>
                <TodoItem>Another thing to do</TodoItem>
                <TodoItem>And one more thing</TodoItem>
            </TodoBackground>
        )
    }
}

export default ToDoList

Context Consumer

The disadvantage of the solution above is that you can only use it in class components. But there is another way that will also work in functional components using the context consumer:

import React from 'react'

import { NavBackground, NavHeader, NavTabs, NavTabsHeader } from './_styles'
import { ThemeContext } from '../context/ThemeContext'

class Navbar extends React.Component {
    render() {
        
        return (
            <ThemeContext.Consumer >
                {(context) => {
                    const { isDarkTheme, darkTheme, lightTheme } = context
                    // if isDarkTheme is true return dark state / else light
                    const theme = isDarkTheme ? darkTheme : lightTheme
                    return (
                        <NavBackground style={theme}>
                            <NavHeader style={theme}>Company Inc.</NavHeader>
                            <NavTabs>
                                <NavTabsHeader>Overview</NavTabsHeader>
                                <NavTabsHeader>Contact</NavTabsHeader>
                            </NavTabs>
                        </NavBackground>
                    )
                }}
            </ThemeContext.Consumer>
        )
    }
 }

 export default Navbar

Toggling State

We now need to add a function that allows the user to toggle the used theme by creating a changeTheme function inside our theme context provider and passing it through to all children:

import React, {createContext, Component} from 'react'

export const ThemeContext = createContext()

class ThemeContextProvider extends Component {
    state = {
        isDarkTheme: true,
        lightTheme: {
            color: 'purple',
            backgroundColor: 'snow',
            text: 'Dark'
        },
        darkTheme: {
            color: 'plum',
            backgroundColor: 'purple',
            text: 'Light'
        }
    }

    changeTheme = () => {
        // toggle theme to opposite when called
        this.setState({isDarkTheme: !this.state.isDarkTheme})
    }

    render() {
        return (
            <ThemeContext.Provider value={{ ...this.state, changeTheme: this.changeTheme }}>
                {this.props.children}
            </ThemeContext.Provider>
        )
    }
 }


export default ThemeContextProvider

I also added another variable to the state called text that we can call on to add the button label to our theme toggle:

import React from 'react'
import {Text} from 'react-native'
import { NavBackground, NavHeader, NavTabs, NavTabsHeader, ThemeToggle } from './_styles'
import { ThemeContext } from '../context/ThemeContext'

class Navbar extends React.Component {
    render() {
        
        return (
            <ThemeContext.Consumer >
                {(context) => {
                    const { isDarkTheme, darkTheme, lightTheme, changeTheme } = context
                    // if isDarkTheme is true return dark state / else light
                    const theme = isDarkTheme ? darkTheme : lightTheme
                    return (
                        <NavBackground style={theme}>
                            <NavHeader style={theme}>Company Inc.</NavHeader>
                            <NavTabs>
                                <NavTabsHeader>Overview</NavTabsHeader>
                                <NavTabsHeader>Contact</NavTabsHeader>
                                <ThemeToggle onPress={changeTheme}>
                                    <Text>{theme.text}</Text>
                                </ThemeToggle>
                            </NavTabs>
                        </NavBackground>
                    )
                }}
            </ThemeContext.Consumer>
        )
    }
 }

 export default Navbar

Nesting Multiple Contexts

Let's add another context that holds the logged-in state of the user:

import React, {createContext, Component} from 'react'

export const AuthContext = createContext()

class AuthContextProvider extends Component {
    state = {
        isLoggedIn: false
    }

    changeAuthStatus = () => {
        this.setState({isLoggedIn: !this.state.isLoggedIn})
    }

    render() {
        return (
            <AuthContext.Provider value={{...this.state, changeAuthStatus: this.changeAuthStatus}}>
                {this.props.children}
            </AuthContext.Provider>
        )
    }
}

export default AuthContextProvider

We want the ToDo List hidden if the logged-in state is false so wrap the list inside the authentication context provider:

import React from 'react'

import { RootView } from './src/components/_styles'
import ThemeContextProvider from './src/context/ThemeContext'
import AuthContextProvider from './src/context/AuthContext'
import Navbar from './src/components/Navbar'
import TodoList from './src/components/ToDo'

const App = () => {
  return (
    <RootView>
      <ThemeContextProvider>
        <Navbar />
        <AuthContextProvider>
          <TodoList />
        </AuthContextProvider>
      </ThemeContextProvider>
    </RootView>
  );
}

export default App

And the ToDo itself now needs to be wrapped inside the consumer:

import React from 'react'
import {Text} from 'react-native'

import { TodoBackground, TodoItem, LoginView } from './_styles'
import { ThemeContext } from '../context/ThemeContext'
import { AuthContext } from '../context/AuthContext'

class ToDoList extends React.Component {
    render() {
        return (
            <AuthContext.Consumer >
                {(authContext) => {
                    return (
                        <ThemeContext.Consumer >
                            {(themeContext) => {
                                const { isLoggedIn, changeAuthStatus }  = authContext
                                const { isDarkTheme, darkTheme, lightTheme } = themeContext
                                // if isDarkTheme is true return dark state / else light
                                const theme = isDarkTheme ? darkTheme : lightTheme
                                if (isLoggedIn) {
                                return (
                                    <TodoBackground style={theme}>
                                        <TodoItem>One thing to do</TodoItem>
                                        <TodoItem>Another thing to do</TodoItem>
                                        <TodoItem>And one more thing</TodoItem>
                                        <TodoItem onPress={changeAuthStatus}>
                                            <Text>Logout</Text>
                                        </TodoItem>
                                    </TodoBackground>
                                )}
                                return (
                                    <LoginView>
                                        <TodoItem onPress={changeAuthStatus}>
                                            <Text>Login</Text>
                                        </TodoItem>
                                    </LoginView>
                                )
                            }}
                        </ThemeContext.Consumer>   
                    )
                }}
            </AuthContext.Consumer>
        )
    }
}

export default ToDoList