Story of React Context Migration

Viktor Shevchenko
5 min readMay 15, 2018

--

As you may know in React 16.3 a new API to work with Context has arrived, and finally it is no longer experimental rather officially supported.

In general you should not use context in your applications and favor state management libraries like MobX or Redux. But if you are a library contributor like me, you probably have to use it. I am author and maintainer of open source solution reactive-mobx-form. This library is build to make form management in React+MobX applications easy and fun. It’s written in TypeScript and inspired by Redux Form and Angular Reactive Forms.

How context was used in reactive-mobx-form

  1. Each Form Component (lets call it ReactiveForm) is produced with HOC. This ReactiveForm contains 2 properties in its context. So all Control (a React Component representing a form field) have access to them. Now they are:
    a. _ReactiveMobxForm: Object — indicating form object in formStore
    b. _destroyControlStateOnUnmount: Boolean — indicating if fields should be removed from formStore when they disappear on screen (consider dynamic forms)

2. Form may contain nested Controls (form fields). As an example ControlArray component is used to aggregate several Control components (Consider form with list of tags, where each tag corresponds to a separate field. Real world example).

The library architecture is designed in a way, that name property <Control name="firstName"> will correspond to key in formStore(your app MobX store). As a result manually writing hierarchical names will be tedious process.

Imagine you will have to track names in Control inside ControlArray. Then name should look like <Control name="[0].firstName"> — this will be a mess. To avoid such case components like ControlArray and ControlSection introduce own context property: _ReactiveMobxFormFieldNamePrefix — a string value with own name, that inner components can consume and build their full name by concatenation of prefix and own prop.name.

To conclude, 2 props were passed down in form context, and 1 in context of ControlArray and ControlSection

Problems Experienced with Existing legacy context API

  1. Despite I am writing a library in typescript, I still had to be dependent on prop-types package. In order to define contextTypes and childContextTypes.
  2. I will not dig deep into the problem, but generally I had to re-render my components when property in context changed. So firstly I had to update data in my context. Here is a description on how to do it. I think the first paragraph “Don’t do it.” completely describes the situation. I had to manually execute this.setState({}) ;) I agree, it does not look nice.
  3. Now we have an official way of working with context. And I’d really like to start using it.

Migration

  1. Create HOC for passing context

Due to form context is consumed by several components (Control, ControlArray, ControlSection). I have decided not to import Context.Consumer in each of them, rather to create a HOC and warp my Controls with it. This approach is React way of solving this kind of issues + allows to save some typing.

The same HOC was also implemented to pass ControlArray name to its sub Control(s) component. But here I will mostly focus on FormContext.

2. Apply context HOC

Here is how HOCs are applied to Control component. (Together with ParentNameContext).

3. Passing context down

Here goes an example like FormUI component passed down its context, an object with 2 properties.

Values for destroyControlStateOnUnmount, wrappedFormComponent, formProps are passed into FormUI form closure. I have just omitted it to reduce complexity.

4. Remove old context

I had removed this 3 pieces of code:

5. Modify context usage

I have went through an app, and replaced usage of context._reactiveMobxForm to props.__formContext.form So no more working with context but with props.

🎉🎉🎉 And it worked 🎉🎉🎉

Yes, really! Its quite possible that usage of TypeScript helped a lot, as ever I made a typo, or tried to access property that was not defined — I was notified about that. But I just did npm start and code worked as expected. I think this one of many benefits when working with React — its predictability and clarity.

Keep in mind

As now all properties that previously were accessible on context.someProp appear to be on props.someProp you can face a situation on name clash (It is low risk if you explicitly using Context.Consume, but probable when using a HOC) so consider an idea of namespacing things that you receive from context. Or at least some static type checking tool, that will prevent you form making such mistake. In my case, I am prefixing name with double underscore.

What to note

Some additional changes were delivered in React 16.3. So beside of context refactoring you may also like to change few additional things.

At first — componentWillMount() life circle hook is considered as unsafe and not recommended to use. Despite this name will still work until React 17, it is already a good time to either get rid of it or to rename it to UNSAFE_componentWillMount() to avoid console warnings in your code.

The second one — componentWillReciveProps(nextProps, nextContext) is also considered unsafe, so either rename it to eliminate warning to UNSAFE_componentWillReciveProps() or follow recommended way and use static life circle hook getDerivedStateFromProps(nextProps, prevState). It differs from old componentWillReciveProps in several ways.

  1. This hook is executed both after a component is instantiated and when it receives new props.
  2. You need to return a new object from this method to indicate what to update in state
  3. Return null from it in case no state update are required.

Still if you need to perform any side effect, make in inside componentDidUpdate method. (Optionally previously combined it with getDerivedStateFromProps)

Conclusion

In total update procedure did not took me too long time, near an hour or so. But this is a library and not a big project. So good luck with your upgrade and have fun!

If you project uses React and MobX — consider using Reactiv Mobx Form and experience how easy form manipulation can be!

--

--