React Native Context & Hooks
- Setup
- Context Consumer vs useContext Hooks
- Creating Context with Functional Components
- Handling To-Do Items using Context
Setup
npm install -g expo-cli
expo init react-native-context-hooks
Choose the blank template and run the client:
cd react-native-context-hooks
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"
Context Consumer vs useContext Hooks
In a previous step I created an React Native application that used the React Context API using Context Consumers in Class components. Thanks to the useContext
hook we can now simplify those components.
Navbar
Before (Class Component)
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>Home</NavTabsHeader>
<NavTabsHeader>Contact</NavTabsHeader>
<ThemeToggle onPress={changeTheme}>
<Text>{theme.text}</Text>
</ThemeToggle>
</NavTabs>
</NavBackground>
)
}}
</ThemeContext.Consumer>
)
}
}
export default Navbar
After (Functional Component)
import React, {useContext} from 'react'
import {Text} from 'react-native'
import { NavBackground, NavHeader, NavTabs, NavTabsHeader, ThemeToggle } from './_styles'
import { ThemeContext } from '../context/ThemeContext'
const Navbar = () => {
const { isDarkTheme, darkTheme, lightTheme, changeTheme } = useContext(ThemeContext)
// 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>Home</NavTabsHeader>
<NavTabsHeader>Contact</NavTabsHeader>
<ThemeToggle onPress={changeTheme}>
<Text>{theme.text}</Text>
</ThemeToggle>
</NavTabs>
</NavBackground>
)
}
export default Navbar
ToDo List
Before (Class Component)
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
After (Functional Component)
import React, { useContext } from 'react'
import {Text} from 'react-native'
import { TodoBackground, TodoItem, LoginView } from './_styles'
import { ThemeContext } from '../context/ThemeContext'
import { AuthContext } from '../context/AuthContext'
const ToDoList = () => {
const { isDarkTheme, darkTheme, lightTheme } = useContext(ThemeContext)
const { isLoggedIn, changeAuthStatus } = useContext(AuthContext)
// 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>
)
}
export default ToDoList
Creating Context with Functional Components
Authentication Context
Before (Class Component)
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
After (Functional Component)
import React, { createContext, useState } from 'react'
export const AuthContext = createContext()
const AuthContextProvider = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false)
const changeAuthStatus = () => {
setIsLoggedIn(!isLoggedIn)
}
return (
<AuthContext.Provider value={{ isLoggedIn, changeAuthStatus}} >
{ children }
</AuthContext.Provider>
)
}
export default AuthContextProvider
Theme Context
In case of the theme context this makes a little less sense - it actually is more readable as a class component.
Before (Class Component)
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
After (Functional Component)
import React, {createContext, useState } from 'react'
export const ThemeContext = createContext()
const ThemeContextProvider = ({ children }) => {
const [isDarkTheme, setIsDarkTheme] = useState(true)
const [lightTheme, setLightTheme] = useState({
color: 'purple',
backgroundColor: 'snow',
text: 'Dark'
})
const [darkTheme, setdarkTheme] = useState({
color: 'plum',
backgroundColor: 'purple',
text: 'Light'
})
const changeTheme = () => {
// toggle theme to opposite when called
setIsDarkTheme(!isDarkTheme)
}
return (
<ThemeContext.Provider value={{ isDarkTheme, changeTheme, lightTheme, darkTheme }}>
{ children }
</ThemeContext.Provider>
)
}
export default ThemeContextProvider
Handling To-Do Items using Context
So far the to-do list simply rendered a few hard coded items. Let's start by copying them into context like:
import React, { createContext, useState } from 'react'
import {v4 as uuidv4} from 'uuid'
export const TodoItemContext = createContext()
const TodoItemContextProvider = ( {children} ) => {
const [items, setItems] = useState([
{ text: 'One thing to do', id: `${uuidv4()}`},
{ text: 'Another thing to do', id: `${uuidv4()}`},
{ text: 'And one more thing', id: `${uuidv4()}`},
])
return (
<TodoItemContext.Provider value={{items}}>
{children}
</TodoItemContext.Provider>
)
}
export default TodoItemContextProvider
To provide this context we have to wrap the list inside the provider tags:
import React from 'react'
import { RootView } from './src/components/_styles'
import ThemeContextProvider from './src/context/ThemeContext'
import AuthContextProvider from './src/context/AuthContext'
import TodoItemContextProvider from './src/context/TodoItemContext'
import Navbar from './src/components/Navbar'
import TodoList from './src/components/ToDo'
const App = () => {
return (
<RootView>
<ThemeContextProvider>
<Navbar />
<TodoItemContextProvider>
<AuthContextProvider>
<TodoList />
</AuthContextProvider>
</TodoItemContextProvider>
</ThemeContextProvider>
</RootView>
);
}
export default App
And then access the context to fill a React Native FlatList using the useContext
function like:
import React, { useContext } from 'react'
import { Text } from 'react-native'
import { TodoBackground, TodoList, TodoItem, LoginView } from './_styles'
import { ThemeContext } from '../context/ThemeContext'
import { AuthContext } from '../context/AuthContext'
import { TodoItemContext } from '../context/TodoItemContext'
const ToDoList = () => {
const { isDarkTheme, darkTheme, lightTheme } = useContext(ThemeContext)
const { isLoggedIn, changeAuthStatus } = useContext(AuthContext)
const { items } = useContext(TodoItemContext)
// if isDarkTheme is true return dark state / else light
const theme = isDarkTheme ? darkTheme : lightTheme
if (isLoggedIn) {
return (
<TodoBackground style={theme}>
{
items.length ? (
<TodoList
data={items}
keyExtractor={(item) => item.id}
renderItem={({item}) => {
return <TodoItem>{item.text}</TodoItem>
}}
/>
) : (
<TodoItem>Nothing to do...</TodoItem>
)
}
<TodoItem onPress={changeAuthStatus}>
<Text>Logout</Text>
</TodoItem>
</TodoBackground>
)}
return (
<LoginView>
<TodoItem onPress={changeAuthStatus}>
<Text>Login</Text>
</TodoItem>
</LoginView>
)
}
export default ToDoList
Adding Items using Context
Start by creating an addItem
function inside the To-Do List component:
import React, { createContext, useState } from 'react'
import {v4 as uuidv4} from 'uuid'
export const TodoItemContext = createContext()
const TodoItemContextProvider = ( {children} ) => {
const [items, setItems] = useState([
{ text: 'Have a great day!', id: `${uuidv4()}`}
])
const addItem = (item) => {
setItems([...items, {text: item, id: `${uuidv4()}`}])
}
return (
<TodoItemContext.Provider value={{items, addItem}}>
{children}
</TodoItemContext.Provider>
)
}
export default TodoItemContextProvider
And add an InputText field + a submit button that can take the text input and forward it to the addItem
function for us:
import React, { useContext, useState, useEffect } from 'react'
import { Text } from 'react-native'
import { TodoBackground, TodoList, TodoItem, LoginView, InputItem } from './_styles'
import { ThemeContext } from '../context/ThemeContext'
import { AuthContext } from '../context/AuthContext'
import { TodoItemContext } from '../context/TodoItemContext'
const ToDoList = () => {
const [todo, setTodo] = useState('')
const { isDarkTheme, darkTheme, lightTheme } = useContext(ThemeContext)
// if isDarkTheme is true return dark state / else light
const theme = isDarkTheme ? darkTheme : lightTheme
const { isLoggedIn, changeAuthStatus } = useContext(AuthContext)
const { items, addItem } = useContext(TodoItemContext)
const handleChange = (text) => {
setTodo(text)
}
const handleAddItem = () => {
if(todo.length > 0)
addItem(todo)
setTodo('')
}
if (isLoggedIn) {
return (
<TodoBackground style={theme}>
{
items.length ? (
<TodoList
data={items}
keyExtractor={(item) => item.id}
showsVerticalScrollIndicator={false}
renderItem={({item}) => {
return <TodoItem>{item.text}</TodoItem>
}}
/>
) : (
<TodoItem>Nothing to do...</TodoItem>
)
}
<InputItem
value={todo}
onChangeText={(text) => handleChange(text)}
/>
<TodoItem onPress={handleAddItem}>
<Text>Add Item</Text>
</TodoItem>
<TodoItem onPress={changeAuthStatus}>
<Text>Logout</Text>
</TodoItem>
</TodoBackground>
)}
return (
<LoginView>
<TodoItem onPress={changeAuthStatus}>
<Text>Login</Text>
</TodoItem>
</LoginView>
)
}
export default ToDoList
Removing Items using Context
Now lets add another function to our context that allows us to remove items from the list:
import React, { createContext, useState } from 'react'
import {v4 as uuidv4} from 'uuid'
export const TodoItemContext = createContext()
const TodoItemContextProvider = ( {children} ) => {
const [items, setItems] = useState([
{ text: 'Have a great day!', id: `${uuidv4()}`}
])
const addItem = (item) => {
setItems([...items, {text: item, id: `${uuidv4()}`}])
}
const removeItem = (id) => {
setItems(items.filter((item) => {
return item.id !== id
}))
}
return (
<TodoItemContext.Provider value={{items, addItem, removeItem}}>
{children}
</TodoItemContext.Provider>
)
}
export default TodoItemContextProvider
We can call this function by pressing an item and providing the item id for the removal in the process:
import React, { useContext, useState } from 'react'
import { Text, TouchableOpacity } from 'react-native'
import { TodoBackground, TodoList, TodoItem, LoginView, InputItem } from './_styles'
import { ThemeContext } from '../context/ThemeContext'
import { AuthContext } from '../context/AuthContext'
import { TodoItemContext } from '../context/TodoItemContext'
const ToDoList = () => {
const [todo, setTodo] = useState('')
const { isDarkTheme, darkTheme, lightTheme } = useContext(ThemeContext)
// if isDarkTheme is true return dark state / else light
const theme = isDarkTheme ? darkTheme : lightTheme
const { isLoggedIn, changeAuthStatus } = useContext(AuthContext)
const { items, addItem, removeItem } = useContext(TodoItemContext)
const handleChange = (text) => {
setTodo(text)
}
const handleAddItem = () => {
if(todo.length > 0)
addItem(todo)
setTodo('')
}
const handleRemoveItem = (id) => {
removeItem(id)
}
if (isLoggedIn) {
return (
<TodoBackground style={theme}>
{
items.length ? (
<TodoList
data={items}
keyExtractor={(item) => item.id}
showsVerticalScrollIndicator={false}
renderItem={({item}) => {
return <TouchableOpacity onPress={() => handleRemoveItem(item.id)}><TodoItem>{item.text}</TodoItem></TouchableOpacity>
}}
/>
) : (
<TodoItem>Nothing to do...</TodoItem>
)
}
<InputItem
value={todo}
onChangeText={(text) => handleChange(text)}
/>
<TodoItem onPress={handleAddItem}>
<Text>Add Item</Text>
</TodoItem>
<TodoItem onPress={changeAuthStatus}>
<Text>Logout</Text>
</TodoItem>
</TodoBackground>
)}
return (
<LoginView>
<TodoItem onPress={changeAuthStatus}>
<Text>Login</Text>
</TodoItem>
</LoginView>
)
}
export default ToDoList