React Native Context API
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 yourpackage.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