React Context vs global values

One topic I can say I commonly hear when discussing with fellow devs is the justification of using React’s Context for “holding” a value when you can:

  • export it from a module
  • attached a global value on the window object
  • define it as and environment variable and let your bundler get it from process.env.

These are all simple and clean (as in obvious) ways to keep global and constant values. By “global” I mean entire-application-global. Same for the “constant” notion, although, you could trigger an explicit re-rendering on the app when a setter function is called.

Bellow I will give an example of these two conditions slightly diverging and that’s when Context shows it’s usefulness. We are omitting props drilling, of course, assuming you’d like to avoid that, since you arrived at deciding between Context or globals.

Take the case of a range of colors for some elements. Let’s say a pallet of colors for a website featuring interior design.

Values exported from a module

For comparison I will tackle only this case of defining global value. Let us create a file where you’ll write the following.

1
2
// colors.js
export const colors = ["#4B6F80", "#E3F6FF", "#96DEFF", "#08608A", "#78B2CC"];

Then import it in a component:

1
2
3
4
5
6
7
// ColoredBlocks.js
import React from "react";
import { colors } from "./colors";

export default function ColoredBlocks() {
return colors.map(c => <div style={{ background: c }} />);
}

Finally, display your pallet.

1
2
3
4
5
6
7
8
9
10
11
12
// App.js
import ColoredBlocks from "./ColoredBlocks";
// other imports ...

function App() {
return (
<div className="App">
<ColoredBlocks />
</div>
);
}
// render root ...

And the visual output would be:

Everything works and looks nice. This is fine when the component tied to the colors is itself a singular element in the app.

Yet, once you need to render, the ColoredBlocks component cannot be reused with other values (remember, prop passing is assumed not to be an option).

Values provided by Context

Many apps have context providers as a single, close to root wrapper around all the app. Be it React Router, Redux, Appolo Graphql, it’s likely you’ll see just one of the providers and lots of consumers arbitrarily spread throughout the code.

However, when actively reading the Context documentation you’ll see the first subtitle “Context provides a way to pass data through the component tree“. A few paragraphs down the page it writes “it will read the current context value from the closest matching Provider above it in the tree“.

We know from a the datastructures 101 course that any subtree is a tree itself. React components are no different. That means we could have providers for several branches.

Let’s redefine the ColoredBlocks component. I’ve defined the context in the same file, but you could easily have it elsewhere.

1
2
3
4
5
6
7
8
9
10
11
12
// ColoredBlocks.js
import React from "react";

export const ColorsContext = React.createContext(["black"]);

export function ColoredBlocks() {
return (
<ColorsContext.Consumer>
{colors => colors.map(c => <div style={{ background: c }} />)}
</ColorsContext.Consumer>
);
}

An leveraging it would like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// App.js
import { ColoredBlocks, ColorsContext } from "./ColoredBlocks";
// other imports ...

const coldColors = ["#4B6F80", "#E3F6FF", "#96DEFF", "#08608A", "#78B2CC"];
const warmColors = ["#801D13", "#FAD6CF", "#E0553D", "#804039", "#CC301F"];

function App() {
return (
<div className="App">
<ColorsContext.Provider value={coldColors}>
{/* ColoredBlocks can be nested however deep */}
<ColoredBlocks />
</ColorsContext.Provider>
<br />
<ColorsContext.Provider value={warmColors}>
<ColoredBlocks />
</ColorsContext.Provider>
</div>
);
}
// render root ...

This produces the desired visual output:


It’s true, one would probably define the color values in a different module or config file, but it’ll only be passed to the ColorsProvider and it won’t “litter” your application code.

Conclusion

That’s one of the advantages the Context API offers over global values. It’s the ability to work with the component tree. Any tree, therefore code (components) can be shared and reused, inside the same app and cross projects. It bring flexibility whilst sticking to a standard. This is part of React’s philosophy, to work with your hierarchy as much as possible. Actually, the mindset is a good practice for software development in general, and is loosely known as the Dependency Injection principle.