Upgrading React to v16 and Enzyme to v3

A couple of weeks ago React v16 was released with some exciting new features including returning arrays from render() functions (no more wrapping <divs>!), better server side rendering and performance improvements. Read more here: https://reactjs.org/blog/2017/09/26/react-v16.0.html

We're not yet using React on the main ReadMe site, but we are on one of our smaller projects which will soon be integrated into main ReadMe (more on this in a later blog post). On this project I decided to give the shiny new React a spin to see if anything needed to change or be refactored.

Luckily our code worked great (it's quite a small codebase)! But when it came to running the tests I noticed some weird failures. I didn't realise at the time that Enzyme would also need to be upgraded (they have a pretty useful migration guide here). I've documented my small problems in migrating below to hopefully help anyone else who has to go through a similar process.

New adapter system

With Enzyme v3 they've cleverly introduced a new adapter system. The purpose of which is so that new (or old) gotchas of different React versions don't leak into the main Enzyme codebase, but are contained within their own adapter. Here's the adapter for React 16: https://www.npmjs.com/package/enzyme-adapter-react-16

Enzyme now needs to be configured with an adapter when it is require()d.

Before:

const { shallow, mount } = require('enzyme');

After:

const Adapter = require('enzyme-adapter-react-16');
const enzyme = require('enzyme');
enzyme.configure({ adapter: new Adapter() });

const { shallow } = enzyme;

Slightly more boilerplate, but it gives more flexibility and allows them to make changes easier in future. If the boilerplate annoys you, you can create a helper file which wraps this up:

const Adapter = require('enzyme-adapter-react-16');
const enzyme = require('enzyme');

enzyme.configure({ adapter: new Adapter() });

module.exports = enzyme;

You can either require this directly, or if you're using Jest, you can add it to the setupFiles array and leave your code as before.

instance.hasClass() not working

Enzyme provides a way to assert whether a class exists or not: http://airbnb.io/enzyme/docs/api/ShallowWrapper/hasClass.html. Right now there appears to be a bug in how this is working: https://github.com/airbnb/enzyme/issues/1177 the workaround is to call render() on the instance first to return the cheerio wrapper. This is working but I've subscribed to the issue for future updates.

Before:

component.hasClass('test');

After:

component.render().hasClass('test');

update() on mount()ed components needs to be manualy called in some cases

This change is well documented in the migration guide here.

If you're making changes by calling instance functions instead of simulating events, you may get bitten by this one.

Before:

component.instance().toggle();
expect(component.find('.text').text()).toBe('toggled');

After:

component.instance().toggle();
component.update();
expect(component.find('.text').text()).toBe('toggled');

component.node is now an inaccessible private member

Accessing component.node previously used to return you with the DOM node, which you could modify freely. For example to modify the value of an input.

Now on access you will get the following error message:

Attempted to access ShallowWrapper::node, which was previously a private property on
Enzyme ShallowWrapper instances, but is no longer and should not be relied upon.
Consider using the getElement() method instead.

Using getElement() didn't work for me, but I found that using instance() was suitable for my uses (found from this github issue).

Before:

component.find('input[type="text"]').node.value = '1234';

After:

component.find('input[type="text"]').instance().value = '1234';

React now requires a requestAnimationFrame polyfill

You may see this in your test or application logs:

Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills

I've added the raf package to jest's setupFiles to fix this as well. Obviously it depends on your end browser support whether you need to polyfill this in your application or not.

Bonus: Remember to update everything

I came across a few other issues because I forgot to update everything and had some version mismatches between all of the required modules. Be sure to run the following otherwise you'll get some weird issues:

npm install react@latest react-dom@latest react-test-renderer@latest enzyme@latest enzyme-adapter-react-16@latest

It may also be a good idea to perform the Enzyme update first (using the React 15 Adapter), then the React update. It may be more difficult to debug issues if you're unsure where problems are coming from.