Enzyme: New `memo` feature from React 16.6 is not supported

Created on 25 Oct 2018  路  24Comments  路  Source: enzymejs/enzyme

Current behavior

Shallow render will fail with error:

Invariant Violation: ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was 'object'

Expected behavior

Render without error.

Your environment

No need, happenes everywhere. I new adaper version will be needed I guess.

API

  • [x] shallow
  • [x] mount
  • [ ] render

Version

| library | version
| ------------------- | -------
| enzyme | 3.7.0
| react | 16.6.0

Adapter

  • [X] enzyme-adapter-react-16
  • [ ] enzyme-adapter-react-16.3
  • [ ] enzyme-adapter-react-16.2
  • [ ] enzyme-adapter-react-16.1
  • [ ] enzyme-adapter-react-15
  • [ ] enzyme-adapter-react-15.4
  • [ ] enzyme-adapter-react-14
  • [ ] enzyme-adapter-react-13
  • [ ] enzyme-adapter-react-helper
  • [ ] others ( )

Most helpful comment

Temporal workaround with jest... :/

jest.mock('react', () => {
  const r = jest.requireActual('react');

  return { ...r, memo: (x) => x };
});

cc. @asdelday

All 24 comments

Yep, it鈥檚 only been 2 days, so we don鈥檛 quite support 16.6 just yet :-)

Meanwhile, a named export of the component for the tests did the trick for me:

export { MyComponent }
export default React.memo(MyComponent)

But the snapshots of the components using MyComponent need to be updated:

- Snapshot
+ Received

- <MyComponent someProp="hello there" />
+ <[object Object] someProp="hello there" />

Another alternative is a wrapper around the enzyme methods. We already use this, so I just added this, and it seems to work:

const unwrapMemo = (shallow: typeof Enzyme.shallow): typeof Enzyme.shallow => (
  node: React.ReactElement<any>,
  options?: Enzyme.ShallowRendererProps,
) => {
  if (typeof node.type === 'object' && (node.type as any).$$typeof === Symbol.for('react.memo')) {
    node = Object.create(node, {
      type: {
        configurable: true,
        enumerable: true,
        value: (node.type as any).type,
      },
    });
  }

  return shallow(node, options);
};

I'd strongly suggest not doing anything that requires exporting an unwrapped component.

@Alxandr that looks suspiciously close to the actual code we'd need to support it in enzyme. Instead of relying on a wrapper, would you be willing to make a PR that directly adds the support to enzyme? 馃槃 馃檹

@ljharb I might look at it, though tbh we're considering moving away from enzyme at work so I doubt I'll get any allocated time for it there 馃槢. Also, the code didn't work for our cases, cause it works when you render simple components, it doesn't deal with the fact that children can also contain memos (which shouldn't be too hard to handle though).

I would be happy to take it if @Alxandr can't, I might need some guidance though as I'm not really familiar with the enzyme codebase besides just using it. I'm working on improving performance for an app and memoizing some components will help.

@pnavarrc that would be great. I already have a TODO list of OSS stuff I need to fix and I'd like to clear out some items before I add more 馃檭

I will start by forking the repo and reading the Contributing Guide and forking the repo to get familiar with the environment.

@pnavarrc is any progress with PR?

Hi, sorry for the delay! I have been getting more familiar with the enzyme codebase, but I think I will need some guidance on what's needed to implement this (tests that will need to be added, maybe an overview of how adapters work, etc), maybe a more experienced contributor can get in touch with me? (my email is on my GitHub profile).

I can still give it a shot, but it would be ideal to get some help to have this ready sooner. If someone else has time and more experience and can get this done faster, I will be happy to help testing or updating the docs.

@pnavarrc I'm traveling through Thanksgiving (so i might be slow to respond), but feel free to ping me anytime in Slack, freenode IRC, or gitter, and I'll help walk you through it.

I created a PR #1914 based out of the above discussion. Please let me know if this is something we can go ahead with.

I get the error when shallow is called with the memoized component, but if I wrap it in a div and then call shallow on it, it seems to work.

Temporal workaround with jest... :/

jest.mock('react', () => {
  const r = jest.requireActual('react');

  return { ...r, memo: (x) => x };
});

cc. @asdelday

@drpicox Thanks, this works for now.

Just to reiterate over @drpicox 's life saving answer. One could also have src/__mocks__/react.js, where you can centralize this hack, to easily remove it later on.

const react = require("react");
module.exports = { ...react, memo: x => x };

Inspired by @Alxandr's solution, and because I often find myself having to _unwrap_ components or exporting _unwrapped_ versions of those components, I've gone ahead and released UnHOC (Unwrap + Higher Order Components). I hope you don't mind me sharing it here:

It's an extendable testing utility with the aim of effortlessly Unwrapping React HOCs allowing you to focus on writing tests instead _unwrapping_ or mocking everything.

For this use case, simply install @unhoc/core and @unhoc/react, initialize it, and wrap your shallow method during the setup and your good to go.

// setupTests.js
import Enzyme, { shallow } from 'enzyme';
import createUnHOC from '@unhoc/core';
import { unHOCMemo } from '@unhoc/react';

// Configure Enzyme as you would
// Enzyme.configure({ adapter: new Adapter() })

const unhoc = createUnHOC({ plugins: [unHOCMemo()] });
Enzyme.shallow = (comp, options) => shallow(unhoc(comp), options);

It's maybe a little obtuse for this specific issue when compared to @drpicox and @icyJoseph's solution(s) but it comes into its own when you have components wrapped in multiple HOCs because you can just add more plugins (already supported plugins: react, react-redux, material-ui, with more to come, and accepting PRs of course 馃槈).

Hopefully some of you will find it beneficial 馃檪
Feedback welcome 馃憤

It's also not supported in mount causing Error: Enzyme Internal Error: unknown node with tag 15.

@wintercounter Could you include in the original issue description that the mount API is affected too.

Done.

Another temporary workaround: implement memo yourself.

const memo = Component => class extends React.PureComponent {
  render() {
    return <Component {...this.props} />
  }
}

It lacks propsAreEqual but it still works.

One step further, to merge @drpicox, @icyJoseph and @dkamyshov workarounds:

In src/__mocks__/react.js ...

import * as React from "react";

// Shim memo
const memo = (Component: React.ReactType) =>
  class extends React.PureComponent {
    render() {
      return <Component {...this.props} />;
    }
  };

module.exports = { ...React, memo };

Note support for the second param of React.memo is not implemented here

Can confirm that the mount API it's affected too

@ljharb i am sorry to bother you, but memo is still not supported by enzyme@^3.11.0. i still get

  Enzyme Internal Error: unknown node with tag 15

when doing mount(<Component />) where Component is a React.memo() wrapped component.

Any idea, what's wrong?

@macrozone make sure you're using the exact same minor version of react, react-dom, and react-test-renderer, as well as the latest enzyme and react adapter. If that doesn't work, please file a new issue.

Was this page helpful?
0 / 5 - 0 ratings