State management in React Native using context
Tags: react, reactnative, javascript
Published: 2019-10-05 13:51:34

---
title: State management in React Native using context
published: true
description: using context to manage application state in react native 
tags: react, react-native, javascript
---

I am quite sure a lot of React and React Native devs are familiar with using Redux to manage application state. A few months back I wrote an [article](https://dev.to/emeka/usecontext-a-good-reason-to-drop-redux-216l) on how you can use context in place of Redux for managing global state in React. It's good practice to always keep state as close to where it is needed as possible and this is quite easy to achieve with React because of the pretty simple API of [react router](https://reacttraining.com/react-router/web/guides/quick-start). On the other hand, this practice can pose some difficulty with React Native because of the quite complex API of [React Navigation] (https://reactnavigation.org/). Although there are other alternatives for navigation in React Native such as react router native, React Navigation appears to be the most commonly used navigation library in react native. So, here is a way devs could structure their context providers in react native:

```jsx
// placing all providers in the app's root

<AuthContext.provider value={authValue}>
  <ArticleContext.provider value={articleValue}>
    <UserContext.provider value={userValue}>
      <Navigator />
    </UserContext.provider>
  </ArticleContext.provider>
</AuthContext.provider>

```
Let's assume Navigator is the navigation component that routes to all other components in the App, then having your context providers setup like above could have a negative impact on your app's performance because it means the entire app will rerender when any of the providers value changes including components that do not need or make use of this update. In this article, I will show us a pretty neat way we can set up our navigation and context so that components are only rendered under providers that they need updates from.

In our example app, we will have users context, articles context and auth context. I will eventually shed light on the articles component to show how we can consume context.

## Creating Contexts

We will start by creating our various contexts. Instead of using my providers directly, I love to abstract them in other components I term as 'controllers'. This makes it easy to isolate and modify the logic for creating and updating context value. The controllers return our providers

This is the content of our auth context:

```jsx

import React, { useReducer, useMemo } from 'react';
import PropTypes from 'prop-types';

const initialState = {
  loggedIn: false,
  user: {}
};

const initialContext = [{ ...initialState }, () => {}];

export const AuthContext = React.createContext(initialContext);

const updater = (state, update) => {
  return { ...state, ...update };
};

export function AuthController(props) {
  const [authState, updateAuth] = useReducer(updater, initialState);
  const value = useMemo(() => [authState, updateAuth], [authState]);

  return (<AuthContext.Provider value={value}>
          {props.children}
          </AuthContext.Provider>);
}

AuthController.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node
  ]).isRequired
};

```

For user context, we have:


```jsx

import React, { useReducer, useMemo } from 'react';
import PropTypes from 'prop-types';

const initialState = {
  user: {}
};

const initialContext = [{ ...initialState }, () => {}];

export const UserContext = React.createContext(initialContext);

const updater = (state, update) => {
  return { ...state, ...update };
};

export function UserController(props) {
  const [userState, updateUser] = useReducer(updater, initialState);
  const value = useMemo(() => [userState, updateUser], [userState]);

  return (<UserContext.Provider value={value}>
          {props.children}
          </UserContext.Provider>);
}

UserController.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node
  ]).isRequired
};

```

and lastly, the article context:


```jsx

import React, { useReducer, useMemo } from 'react';
import PropTypes from 'prop-types';

const initialState = {
  articles: []
};

const initialContext = [{ ...initialState }, () => {}];

export const ArticleContext = React.createContext(initialContext);

const reducer = (state, action) => {
  switch (action.type) {
    case "get":
      return {...state, articles: action.articles }
    case "add":
      return { ...state, articles: [...state.articles, action.article] };
    case "delete":
      const articles = [...state.articles];
      const filteredArticles = articles.filter(article => article.id !== action.articleId);
      return { ...state, articles:filteredArticles };
    default:
      throw new Error("Unrecognized action");
  }
};


export function ArticleController(props) {
  const [articleState, dispatch] = useReducer(reducer, initialState);
  const value = useMemo(() => [articleState, dispatch], [articleState]);

  return (<ArticleContext.Provider value={value}>
          {props.children}
          </ArticleContext.Provider>);
}

ArticleController.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node
  ]).isRequired
};

```


That's all our contexts. We pass in an array with two items as the value to our context provider. The first item in the array is our state and the second is a function that updates state. This value has to be [memoized](https://reactjs.org/docs/hooks-reference.html#usememo) to prevent continuous rerendering due to the value receiving a new reference every time the component is rendered.

## Splitting navigation and context providers 

First, we will start by creating our main navigation. Ensure you have react-navigation installed

```cli
npm i react-navigation

```
We will define our main navigator, which is a combination of sub navigators.

```javascript
import { createStackNavigator, createAppContainer } from 'react-navigation';
import User from './user/';
import Article from './articles/';


const Navigator = createStackNavigator(
  {
    user: {
      screen: User
    },
    article: {
      screen: Article
    }
  },
  {
    initialRouteName: 'article'
  }
);

export default createAppContainer(Navigator);

```

Then we create a sub navigator for components relating to the user profile.


```jsx
import React from 'react';
import { createStackNavigator } from 'react-navigation';
import PropTypes from 'prop-types'
import UserDetails from './user-details.js';
import EditUser from './edit-user.js';
import UserController from '../contexts/user-context.js'

const UserNavigator = createStackNavigator({
  userDetails: {
    screen: UserDetails
  },
  editUser: {
    screen: Edituser
  }
}, 
{
  initialRouteName: 'userDetails',
});

export default function User(props) {
  return (
    <UserController>
      <UserNavigator navigation={props.navigation} />
    </UserController>
  );
}

User.router = UserNavigator.router

User.propTypes = {
  navigation: PropTypes.object
};

```

And similarly, a sub navigator for article related components

```jsx
import React from 'react';
import PropTypes from 'prop-types'
import { createStackNavigator } from 'react-navigation';
import ListArticles from './all-articles.js';
import AddArticle from './add-article.js';
import ArticlesController from '../contexts/article-context.js'

const ArticleNavigator = createStackNavigator({
  listArticles: {
    screen: ListArticles
  },
  addArticle: {
    screen: AddArticle
  }
}, 
{
  initialRouteName: 'articleDetails',
});

export default function Article(props) {
  return (
    <ArtileController>
      <ArticleNavigator navigation={props.navigation} />
    </ArticleController>
  );
}

Article.router = ArticleNavigator.router

Article.propTypes = {
  navigation: PropTypes.object
};

```

What we have done so far is splitting our navigators so that we can wrap each one in its respective provider. Our controllers render the providers. What about our auth context? Since authentication can be a concern across our entire app, we can wrap our entire navigator in it so that every component has access to auth state.

```jsx

import React from 'react';
import Navigator from './navigator';
import { AuthController } from './context/auth-context';

export default function App() {
  return (
    <AuthController>
      <Navigator />
    </AuthController>
  );
}

```

Instead of placing all the paths in our main navigator, we've broken them down into various sub navigators and will render them as children of their respective providers and also import them in the main navigator. To learn more about navigation in react native, you can check out the [react navigation docs](https://reactnavigation.org/docs/en/getting-started.html).


## Consuming Contex

Next step, we consume our contexts. In our ListArticles component, here is how we are consuming the articles context.

```jsx
import React, {useEffect, useContext} from 'react';
import {Text, FlatList, ScrollView, TouchableOpacity} from 'react-native';
import PropTypes from 'prop-types';
import {getArticles, removeAricleFromDatabase} from 'api';
import {ArticleContext} from './context/article-context';

export default function ListArticles (props) {
  const [articles, dispatch] = useContext(ArticleContext);
  
  useEffect(() => {
    getArticles()
      .then(articles => dispatch({type:'get', articles})
  }, []);


  const deleteArticle = (article) => {
  removeArticleFromDatabase(article)
    .then((data) => dispatch({type: 'delete', articleId: data.id}));


  const Item = ({id, title}) => {
   return (
     <View>
      <Text>{item.title}</Text>
      <TouchableOpacity onPress={(id) => deleteArticle(id)}>
       <Text>x</Text>
      </TouchableOpacity>
     </View>
   )
  }  

  return (
    <ScrollView>
      <FlatList
        data={articles}
        renderItem={({item}) => <Item title={item.title} id={item.id}/>}
        keyExtractor={item => item.id}
      />
      <TouchableOpacity 
       onPress={() => props.navigation.navigate('addArticle')}>
       <Text>Add new article</Text>
      </TouchableOpacity>
    </ScrollView>
  );

}

```

We are consuming the articles context here using react's [useContext](https://reactjs.org/docs/hooks-reference.html#usecontext) hook. We pass our context as a parameter to the hook and it returns the value passed in the provider. Dispatching actions we want to carry out updates our context provider value. We won't get our values if the provider is not present in the component tree hierarchy.

Similarly, we can dispatch an action for adding an article.

```javascript
import React, {useContext} from 'react';
import {ArticleContext} from './context/article-context';
import {saveArticleInDatabase } from 'api';

const [_, dispatch] = useContext(ArticleContext);

const addArticle = (article) => {
  saveArticleInDatabase(article)
    .then((data) => dispatch({type: 'add', article: data}));
}

/* render beautiful jsx */

```

Every other context we have in our app can be consumed in the same fashion, with each context provider only parent to components that consume it in order to prevent unnecessary re-renders.

None of the patterns adopted here is cast in stone. This is just a guide to optimally use context to manage our React native application's state. To learn more about React context, [here](https://reactjs.org/docs/context.html) is something from the official react docs.