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
.
Just to correct some inaccuracies in the above, we were not effectively doing:
but rather:
Same result though: if
ForecastChart
uses hooks then you'll see "invalid hook call" errors. Needs to be a React element:The eventual fix is this.