Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@wincent
Last active March 12, 2020 12:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wincent/a6d6ecdb79414c4426c729a0ec670576 to your computer and use it in GitHub Desktop.
Save wincent/a6d6ecdb79414c4426c729a0ec670576 to your computer and use it in GitHub Desktop.

This just came up again in a question from the commerce folks so I am documenting here my reply to Fabio in case anybody stumbles on this thread in search in the future:

They had code like this that was failing:

import {render} from 'frontend-js-react-web';
import React from 'react';
import ForecastChart from './ForecastChart.es';
export default function(id, props) {
	render(
		ForecastChart,
		props,
		window.document.getElementById(id)
	);
}

With:

https://reactjs.org/docs/error-decoder.html/?invariant=321

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem. Because you need a layer of indirection:

const Wrapper = props => {
    return <ForecastChart {...props} />;
};

export default function(id, props) {
    render(
        Wrapper,
        props,
        document.getElementById(id)
    );
}

And that is because we changed the initial design:

https://github.com/liferay/liferay-portal/commit/31f9beca59c7ac9b1c42d3e34a842167cb8313d7

Which used to just take an element, to instead expect a renderFunction:

https://github.com/liferay/liferay-portal/commit/e77295d3d2e6ebf0010fdaccf21d09a5d5d6a2d4

Fabio's code was therefore as if he had:

ReactDOM.render(ForecastChart(renderData))

Which obviously won't work because ReactDOM.render expects a React element, not a component function (https://reactjs.org/docs/react-dom.html#render). This would work:

ReactDOM.render(<ForecastChart {...renderData} />);

Because it is equivalent to:

ReactDOM.render(React.createElement(ForecastChart, renderData));

Fabio's question was why did we do renderFunction(renderData) in e77295d3d2e6ebf00 instead of just <Component {...renderData} />?

I don't really remember, and the commit message doesn't fully explain it, but I believe it's to force a layer of indirection where you can split props and context etc. It is a bit confusing for the user, because the Wrapper component in my example above sure looks like a functional component, but again, note that we call the function directly and pass the result — which must be a React element — to ReactDOM.render.

@wincent
Copy link
Author

wincent commented Mar 12, 2020

Just to correct some inaccuracies in the above, we were not effectively doing:

ReactDOM.render(ForecastChart(renderData))

but rather:

ReactDOM.render(
  <Something>
    {ForecastChart(renderData))}
  </Something.
);

Same result though: if ForecastChart uses hooks then you'll see "invalid hook call" errors. Needs to be a React element:

ReactDOM.render(
  <Something>
    {<ForecastChart {...renderData} />}
  </Something.
);

// equivalent to:
ReactDOM.render(
  <Something>
    <ForecastChart {...renderData} />
  </Something.
);

// which is equivalent to:
ReactDOM.render(
  <Something>
    {React.createElement(ForecastChart,  renderData)}
  </Something.
);

The eventual fix is this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment