React Native’s Context Dilemma

Gustavo Machado
The React Native Log
6 min readJul 14, 2016

At first sight, React Native’s context may seem like a cool and powerful thing, however finding really useful use cases for it can prove to be hard. Even when we find valid use cases for using context, implementing components that effectively leverage the context can be tricky sometimes. In this article I explore the context in depth.

Before you continue I strongly recommend you read the React documentation on context, specially on how to implement it!

And please notice that the API is likely to change:

Note: Context is an advanced and experimental feature. The API is likely to
change in future releases.

Why use Context in the first place?

Context allows you to pass data from a parent node to all it’s children. While this is cool, there are already other ways to pass data around in react, so how is context different?

The two most common ways to pass data around are:

  • through props
  • through a module/dependency (aka: globals)

Passing data through props

Even though props is the standard way to pass data around, there are cases where it might become cumbersome. Think for example having to pass the “current locale” or “theme colors” to every visual component through it’s props. Let me show you how bad this looks:

<Menu locale={this.props.locale}>
<MenuItem locale={this.props.locale}>Home</MenuItem>
<MenuItem locale={this.props.locale}>Account</MenuItem>
<MenuItem locale={this.props.locale}>Help</MenuItem>
</Menu>

Clearly, props is not the right mechanism for this.

Another thing that is not easily addressable with props is, what if you your component allows “children” to defined from outside your component? You would then have to transverse your entire children tree and add these props so any children can use it (even when they might not need it!). Again, clearly not ideal.

Using “globals”

You could import a module (adding a dependency) to your components.

Let’s see how this would work. Suppose you have a single module where you define your defaults for a theme (color, font, etc…). Then you use this module to create your UI components. Suppose we have our defaults in the “./theme.js” file:

//extremely-simplistic-button.js
import React, { Component, PropTypes } from ‘react’;
import {
StyleSheet,
TouchableHighlight,
Text
} from ‘react-native’;
//import global theme config
import Theme from ‘./theme’;
export default class Button extends Component {
static propTypes = {
text: PropTypes.string,
onPress: PropTypes.func
}
render() {
return (
<TouchableHighlight style={ styles.container } style={ this.props.onPress }>
<Text style={ styles.text }>
{ this.props.text }
</Text>
</TouchableHighlight>
);
}
}
const styles = StyleSheet.create({
container: {
height: 60,
backgroundColor: Theme.PrimaryColor,
justifyContent: ‘center’,
alignItems: ‘center’,
alignSelf: ‘stretch’
},
text: {
color: Theme.PrimaryTextColor
}
});

Notice how the styles are defined using Theme’s properties.

Because the Theme is imported from everywhere you need it, you are actually using the same reference from all the components.

If you have to be able to change the Theme while running the app, then things get a bit more complicated. One solution is to subscribe to changes on the Theme from the UI components and use the state to cause a re-render of the component. Here’s how this could look:

  • from your components subscribe to changes to Theme
  • change the global theme and trigger listeners
  • update your component’s state to cause a re-render
  • calculate your styles in the render stage
import Theme from ‘./theme’;
//… in your component…
constructor(props) {
super(props);
Theme.onChange(this.handleChangeTheme);
}
state = {
theme: Theme
};
handleChangeTheme(theme) {
this.setState({theme: theme}); //this will cause a re-render
}
render() {
let styles = {
container: {
backgroundColor: this.state.theme.primaryColor
//…

Now this doesn’t look too bad, and it’s actually quite simple. I strongly recommend considering “globals” prior to context, however it has some limitations.

First of all, it couples your components to the ‘./theme.’ module. This is not a big problem in most cases, but it might be an issue if you plan to publish different UI components separately, where each component would have it’s own Theme instance.

The other problem is the fact that changes to the Theme affect every single component. So what would happen if you wanted 5 UI components with one Theme, and 5 other components with another Theme? One alternative is to override the global theme with a theme passed through props. But as we saw previously, having to pass the 5 components an overriding theme through props is cumbersome.

Context

There are some valid use cases for using context, but I think it boils down to “scoping” and perhaps “decoupling” your components.

Scoping

From the documentation:

The best use cases for context are for implicitly passing down the logged-in user, the current language, or theme information. All of these might otherwise be true globals, but context lets you scope them to a single React subtree.

The key word here being “subtree”.

With context what you can do is define different scopes for different subtrees. So going back to our previous example where you need to use two different themes for different components, with context you could achieve the following:

<View>
<Container theme={ Theme.light }>
<MyComp1 />
<MyComp2 />
<MyComp3>
<MyComp4 />
</MyComp3>
<MyComp5 />
</Container>
<Container theme={ Theme.dark }>
<MyComp1 />
<MyComp2 />
<MyComp3>
<MyComp4 />
</MyComp3>
<MyComp5 />
</Container>
</View>

As a matter of fact, this technique was used in by the team on Nativebase.io and might be one of the best examples of context being properly used:

import {Container, Content, Badge} from ‘native-base’;
import React, {Component} from ‘react-native’;
import myTheme from ‘./Themes/myTheme’;

export default class ThemeBadgeExample extends Component {
render() {
return (
<Container>
<Content theme={myTheme}>
<Badge>2</Badge>
<Badge primary>2</Badge>
<Badge success>2</Badge>
<Badge info>2</Badge>
<Badge warning>2</Badge>
<Badge danger>2</Badge>
</Content>
</Container>
);
}
}

You can take a look here for the implementation (it’s beautifully simple):
https://github.com/GeekyAnts/NativeBase/blob/master/Components/Base/NativeBaseComponent.js.

Decoupling

From the documentation:

Do not use context to pass your model data through components. Threading your data through the tree explicitly is much easier to understand. Using context makes your components more coupled and less reusable, because they behave differently depending on where they’re rendered.

While I totally agree that it’s a terrible idea to pass your model data through context, I somewhat disagree with the sentence about coupling.

It is less reusable to use context when you can use props instead, but I think it’s more reusable than using globals. If you were to publish two components, each would have it’s own “Theme” for example. Context allows you to define a contract (the static property “contextTypes”) and give consumers control over which parent component provides this data.

As for the “they behave differently depending on where they’re rendered”, I believe it is the goal of “scoping” (see previous section) which doesn’t make it less reusable. If done correctly it might even make it more reusable.

Updating your context

According to the documentation, context has an important limitation:

If a context value provided by a component changes, descendants that use that value won’t update if an intermediate parent returns false from shouldComponentUpdate. See issue #2517 for more details.
I recommend you scan through issue #2517, since it gives a ton of insight about context and might as well be the very reason why this feature has been experimental for so long.

This means that if I change the theme in runtime, some components with “shouldComponentUpdate” might not re-render.

So in this example:

<Container theme={dark}>
<IReturnFalseInShouldComponentUpdate>
<Button>I need the context</Button>
</IReturnFalseInShouldComponentUpdate>
</Container>

If I change the “theme” prop in Container, the Button component won’t be re-rendered because “IReturnFalseInShouldComponentUpdate” will not re-render.

Hint: shouldComponentUpdate is one of the things redux’ connect uses.

There is however one way to overcome this shouldComponentUpdate problem, but unfortunately it’s rather messy. You can expose a subscriber method in the context, and let children subscribe to changes. So instead of waiting for the component to be re-rendered based on a new set of props, it can be proactively re-rendered based on callbacks from the context.

You can take a look at this is done in react-native-layout-provider (warning, code can be hard to grasp):
https://github.com/jhen0409/react-native-layout-provider/blob/master/src/getLayout.js

Conclusion

I wouldn’t jump straight into using Context without going through the alternatives first.

“Scoping” is by far the best scenario for leveraging context.

--

--