Coming to terms with React context

tl;dr: Rik does some learning about React contexts, then builds himself a (very) simple proof-of-concept thingy which may turn out to be the ‘right way’ to do this sort of stuff

React, Redux and all the props stuff

React is a way of building front end apps that has been intriguing me for a couple of years now. It’s one of those frameworks that entices me with its promises of Shiny while at the same time repulsing me with its sack-full of gotchas and huh-moments which can leave me feeling lost and stupid for days.

The biggest problem I’ve had with React is my usual do-it-yourself approach to learning the framework. I like to think that if I had the opportunity to work as part of a competent team of developers working on a big React/FluxOrRedux system then I’d tame this beast of a conundrum within a couple of weeks (are Facebook hiring? Asking for a friend).

I have built some stuff with React (side projects at work) and I do enjoy coding with the framework. Mixing HTML into my Javascript files doesn’t worry me - it used to be my approach to PHP before that language turned slick and professional on me. React states and passing props down chains of components can - at times - be a joy.

However my biggest problem with React has been state and props, particularly when dealing with values which get used in many, many React components scattered all over the build. Especially when those values - like user details - can be changed and updated by user actions in a component which I code up then somehow forget about.

Fluxing and Reduxing

It turns out that my problems are not down to me. Facebook developers themselves faced the problem of long prop chains and developed a solution to the mess even before they took the whole React thing public. Flux is a fascinating read, but I’ve never coded a single thing containing a Flux Dispatcher.

This is because Flux is hard. Facebook themselves admit that; they suggest using something else instead (because they are Coding Deities and I am not). The thing that gets suggested most often is Redux, specifically React-Redux. Just like Flux, Redux solves the problem of long props chains - it just does it in a different - allegedly simpler - way.

Strangely I’ve never coded a single thing containing a Redux reducer function. Redux always seemed like overkill for my little projects - until my little projects grew container chains that could stretch from England to France, by which time it was always too late.

Getting things into Context

React ships with something called ‘Context’ - a built-in solution to the long-props-chain problem. However the React engineers always recommended using solutions like Redux rather than Context. Context, they said, had ‘issues’. Like a good boy with no time, I followed the advice and wandered off to be flummoxed by Redux reducers instead.

This all changed earlier this year, when the React folks finally got around to fixing their Context’s issues. The warning banners that used to spatter the Context documentation have all disappeared. Context is our new saviour!

Well - sort of saviour. As the documentation makes clear, React Context solves the problem of lots of Components dotted all over the build needing access to the same small set of data: a locale string, for instance, or some site theme styles. But this is the sort of functionality I want. The experiments I’ve built tend to include users logging into a site and it’s that user data I need to share across my Components.

It was time for me to revisit the world of React to do me some learning!

Building a React App that uses Context

Rather than explain what I’ve done in reams of paragraphs, I’m just gonna throw the code into this blog post. I started with the usual create-react-app skeleton and tinkered. Have I mentioned how much I adore create-react-app? It is one of the most beautiful things I’ve ever encountered outside of nature. Thank you, Facebook engineers, for building and sharing create-react-app with me!

index.js

1
2
3
4
5
6
// this is all entirely normal stuff for the index.js file
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

My learning was certainly not all plain sailing. The official documentation is a bit hit-and-miss with its examples and Googling for solutions doesn’t bring up a heavy crop of results. What follows is what I think makes for a reasonable solution; it is certainly not the Last Word, and I’m looking forward to seeing how other coders solve the particular issues I encountered as I tinkered with my experiment.

My eventual solution for using a multi-attribute Context was to package the whole thing into its own ‘component’.

UserContext.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import React, { Component } from 'react';
import moment from 'moment';

// initialize the context
const UserContext = React.createContext();

// give the consumer part of the context a nice name
const UserConsumer = UserContext.Consumer;

// give the provider part a nice name too, with added extras
class UserProvider extends Component {

// provider needs some state for all the user attributes
constructor(props) {
super(props);

// have a single function for updating the state
this.updateUser = (items) => {
console.log('updating context', items);
this.setState({
key: (items.key != null) ? items.key : this.state.key,
name: (items.name != null) ? items.name : this.state.name,
email: (items.email != null) ? items.email : this.state.email,
locale: (items.locale != null) ? items.locale : this.state.locale,
token: (items.token != null) ? items.token : this.state.token,
tokenExpires: (items.tokenExpires != null) ? items.tokenExpires : this.state.tokenExpires,
});
};

// make the updater part of the state so we can pass it on to consumers
this.state = {
key: 'myKey',
name: 'myName',
email: 'myEmail',
locale: 'England',
token: 'myToken',
tokenExpires: moment().format('DD MMM YYYY - HH:mm:ss') + 'hrs',
updateUser: this.updateUser
};
}

// purely for testing
componentDidMount(){
console.log('initial state', this.state);
}

// render the provider as a Higher Order Component thing
render() {
return (
<UserContext.Provider value={this.state}>
{this.props.children}
</UserContext.Provider>
);
}
};

// export both context parts under their nice new names
export {
UserProvider,
UserConsumer
};

The rest of the solution involves three normal, nested components.

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';

// providers have to go higher up the DOM
// - then consumers can feed off the provider as necessary
import { UserProvider } from './UserContext.js';
import SomeIntermediateReactComponent from './SomeIntermediateReactComponent.js';

// Wrap the App Component in the context provider
class App extends Component {

render() {
return (
<UserProvider>
<div className="App">
<SomeIntermediateReactComponent />
</div>
</UserProvider>
);
}
}

export default App;

SomeIntermediateReactComponent.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { Component } from 'react';
import HelloRik from './HelloRik.js';

// nothing special here
// - no props being passed through this component
// - no state to worry about
class SomeIntermediateReactComponent extends Component {

render() {
return (
<div className="SomeIntermediateReactComponent">
<HelloRik />
</div>
);
}
}

export default SomeIntermediateReactComponent;

All the fun stuff of consuming my context happens in the final Component.

HelloRik.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import React, { Component } from 'react';
import moment from 'moment';

// here we shall consume some context stuff
import { UserConsumer } from './UserContext.js';

class HelloRik extends Component {

render() {

// going to have a number of HTML elements display and use various context attributes
return (
<div className="HelloRik">

{/* I can extract the relevant context attributes using the {curly braces} */}
{/* - selected attributes get fed into a function which spits out HTML stuff */}
<UserConsumer>
{({locale}) => <p>Hello, Rik. Your locale is: <b>{locale}</b></p> }
</UserConsumer>

{/* wrapping the HTML stuff in (round brackets) so I can see it more easily */}
<UserConsumer>
{({token, tokenExpires}) => (
<p>Your token &laquo;{token}&raquo; expires at: <i>{tokenExpires}</i></p>
)}
</UserConsumer>

{/* I want to trigger updates to the context, but can't seem to do it directly */}
{/* - instead we can build a local controller component ('HelloRikUpdateButton') */}
{/* - and pass it the 'locale' context attribute, and the 'updateUser' function */}
<UserConsumer>
{({locale, updateUser}) => (
<HelloRikUpdateButton
currentLocale={locale}
updateUser={updateUser} />
)}
</UserConsumer>
</div>
);
}
}

// this is a sort of local controller
// - passing some context values (and the update function) to it as props
class HelloRikUpdateButton extends Component {

render() {

// just saving some typing
let p = this.props;

// a local change function that uses the context stuff
let changeLocale = (e) => {

// for proof of concept, just hop across the Channel
let loc = (p.currentLocale === 'England') ? 'France' : 'England';

p.updateUser({
locale: loc,

// updating the tokenExpires context attribute here
// - just to see if the updateUser function works correctly
tokenExpires: moment().format('DD MMM YYYY - HH:mm:ss') + 'hrs'
});
};

// render our local controller as a clickable button
return (
<p>
<button onClick={changeLocale} >
Change locale
</button>
</p>
);
}
}

// only exporting the main component, not the local controller thing
export default HelloRik;

So does it work?

It works for me!

Of course I am not a React expert. If people spot obvious flaws in my code then please don’t hesitate to let me know in the comments. Pointing out the Stupid Bits is how Rik learns best!