Invalid hook creating Liferay React component - reactjs

I'm trying to create custom react components based on Liferay's Clay components.
Using e.g. just a ClayButton works, but as soon as i try to use hooks (like React.useState), the browser console tells me:
Minified React error #321; visit https://reactjs.org/docs/error-decoder.html?invariant=321 for the full message
The full message tells me i could be using mismatching versions of react and react-dom. I'm not.
I also don't have 2 different versions of react, according to the test described there.
I created a minimal example module at https://github.com/ReFl3x0r/liferay-react-component-test which can be tested in a Liferay Gradle Workspace.
There's also an older thread in Liferay Forums discussing this error, but with no solution.
(https://liferay.dev/ask/questions/development/re-lr-7-3-react-portlet-invalid-hook-call)
What am i doing wrong?
EDIT:
Trying to point out the main code snippets.
First CustomButtonFail.es.js:
import React from 'react';
import ClayButton from '#clayui/button';
const CustomButton = () => {
const [name, setName] = React.useState('test');
return (
<ClayButton displayStyle='primary'>
TEST
</ClayButton>
);
}
export default CustomButton;
The package.json:
{
"dependencies": {
"#clayui/button": "^3.40.0",
"#clayui/css": "3.x",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"devDependencies": {
"#liferay/npm-scripts": "47.0.0",
"react-test-renderer": "^16.12.0"
},
"name": "component-test",
"scripts": {
"build": "liferay-npm-scripts build"
},
"version": "1.0.0"
}
The view.jsp including the component (shortened):
<%#taglib uri="http://liferay.com/tld/react" prefix="react" %>
<div class="react-component-failing">
<react:component
module="js/CustomButtonFail.es"
/>
</div>

I finally got it working. Reducing package.json like this:
{
"devDependencies": {
"#liferay/npm-scripts": "47.0.0"
},
"name": "component-test",
"scripts": {
"build": "liferay-npm-scripts build"
},
"version": "1.0.0"
}
and adding a ".npmbundlerrc" in modules root with content:
{
"config": {
"imports": {
"frontend-taglib-clay": {
"#clayui/button": ">=3.40.0",
"#clayui/css": ">=3.x"
},
"#liferay/frontend-js-react-web": {
"react": ">=16.12.0"
}
}
}
}
did the trick.
Working example is at https://github.com/ReFl3x0r/liferay-react-component-test/tree/working

Related

Are dynamic and external React components possible in runtime?

We're developing a dashboard React-application in which other (trusted) developers can create their own React-widgets in separate environments. The goal would be a situation where new widgets wouldn't require updation to the dashboard-application and instead, these external React-components could be loaded at runtime.
So developer A would have his own project, where he would bundle a React-component into a js-file, which would be forwarded to a hosting site, S3 for example where the dashboard application could read them via a string, at runtime.
For example a weather-widget:
import React, {useState} from "react";
function Weather() {
const [city, setCity] = useState("city");
const weatherUri = `https://wttr.in/${city}_tqp0.png`;
return (
<div>
<input type="text" value={city} onChange={(e) => setCity(e.target.value)}/>
<div>
<img src={weatherUri} alt={city}/>
</div>
</div>
);
}
export default Weather;
This could be then imported to the dashboard-application. I've understood that React's dynamic code-splitting with dynamic url would work here. Either via a situation where widget would have its own React-library, or it'd get it from the dashboard-application. We've, in fact, made this to work, but as soon as we add a state to the component, it breaks:
Uncaught TypeError: Cannot read properties of null (reading 'useState')
at Object.useState (react.development.js:1612:23)
at Weather (weather_widget.js:36:33)
We've configured the widget-component's configuration as follows (React-library will be imported from the dashboard-application):
// ====== vite.config.js ========== (widget)
export default defineConfig({
plugins: [react()],
define: {
'process.env': {}
},
build: {
lib: {
entry: 'src/lib/Weather.tsx',
name: 'Weather',
fileName: 'weather',
formats: ['es']
},
rollupOptions: {
plugins: [
externalGlobals({
react: "React"
})
],
external: ['react'],
output: {
globals:{
react: 'React'
}
}
}
},
})
// ===== package.json ===== (widget)
{
"name": "weather",
"private": true,
"version": "0.0.0",
"type": "module",
"files": [
"dist"
],
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0",
"rollup-plugin-external-globals": "^0.6.1"
},
"devDependencies": {
"#types/node": "18.7.23",
"#types/react": "18.0.21",
"#types/react-dom": "^18.0.6",
"#vitejs/plugin-react": "^1.0.7",
"path": "^0.12.7",
"typescript": "^4.6.4",
"vite": "^2.8.0"
}
}
This is how we've tried to import it to the dashboard-application:
export const WidgetCard = (widgetId: string) => {
const WidgetComponent = React.lazy(() =>
import(`/path/to/${widgetId}.js`));
return
<>
<Suspense fallback={<Loading/>}>
<WidgetComponent/>
</Suspense>
</>
)
});
For the final product, the logic would be as follows:
User will generate a JS-bundle of their React-component
User will call some sort of API, which will save bundle to a hosting site and also add the new widget to a database of some sort.

React compiler end up with Unexpected token <

I am beginner in React and I am struggling with compiler error. Let me introduce my situation. I have two independent React applications:
App A - Big ERP
App B - "Plugin" to the App A
I supposed I will develop App B as an independent application. Then, I will install it to the App A (using npm install git#github.repo/...) once I finish development of the App B. I expected I will call components from App B within the App A source code. Everything went fine until I run the compilation. I am receiving:
SyntaxError: /frontend/node_modules/connector_frontend/src/views/Connector/FormView/index.js: Unexpected token
In my /frontend/node_modules/connector_frontend/src/views/Connector/FormView/index.js there is following code:
const ConnectorFormView = ({ AppValues, secureFetch, ...rest }) => {
return (
<p>Hello world</p>
)
}
export default ConnectorFormView;
Error is ocuring at the position of <p>.
I call this functional component from App A (frontend/src/views/Connector/ConnectorNewEditView/index.js) like this
import ConnectorFormView from "connector_frontend/src/views/Connector/FormView";
const ConnectorNewEditView = () => {
return (<ConnectorFormView AppValues={appValues} secureFetch={secureFetch} />)
}
export default ConnectorNewEditView;
I tried to return just a plain text from the ConnectorFormView component like this:
const ConnectorFormView = ({ AppValues, secureFetch, ...rest }) => {
return (
'Hello world'
)
}
export default ConnectorFormView;
and it was compiled successfully, but once I return a JSX from the ConnectorFormView component the compiler get crashed.
Can anyone explain the source of this error please?
I successfully figured out the source of this problem and also I found a solution. I expected I can reuse however React component implemented in JSX. I expected, the only requirement is to install it, resp. download it in JSX form to my project using NPM. It's FALSE.
According to my research, I can reuse React components compiled from JSX to ES only. It is achievable using Babel. Another very important requiremen, it should not by installed as React application as I installed it. It must be installed as a NPM package with clearly defined set of exported components.
I found this paper https://levelup.gitconnected.com/publish-react-components-as-an-npm-package-7a671a2fb7f and I followed it step by step and finally I achieved desired result.
I can very briefly mention steps which I performed:
Implemented my custom component (mentioned above)
const ConnectorFormView = ({ AppValues, secureFetch, ...rest }) => {
return (
<p>Hello world</p>
)
}
export default ConnectorFormView;
Added the component to /src/index.js file in order to export it for host App
import ConnectorFormView from './components/ConnectorFormView';
export { ConnectorFormView };
Installed Babel and all needed presets
npm install --save-dev #babel/core #babel/cli #babel/preset-env
npm install -save #babel/polyfill
configured Babel - created file /babel.config.json with following content
{
"presets": [
[
"#babel/env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
],
"#babel/preset-react"
]
}
Added new script to the package.json to compile React components to ES:
"build": "rm -rf dist && NODE_ENV=production babel src --out-dir dist --copy-files";
Executed npm run build to compile React components to ES. Compiled components are located in new dist folder.
Adjusted package.json that looks like this now
{
"name": "my_react_components",
"version": "0.1.0",
"private": true,
"description": "Some description",
"author": "Lukas",
"main": "dist/index.js",
"module": "dist/index.js",
"files": [ "dist", "README.md" ],
"dependencies": {
.
.
},
"scripts": {
"build": "rm -rf dist && NODE_ENV=production babel src--out-dir dist --copy-files",
.
.
.
},
"eslintConfig": {
"extends": "react-app"
},
"devDependencies": {
"#babel/cli": "^7.17.10",
"#babel/core": "^7.18.5",
"#babel/preset-env": "^7.18.2"
}
}
Pushed dist folder to the git.
Installed repository to the host project using NPM
npm install https://github.com/my_custom/react_component.git
Import and use the component from installed git repo
import ConnectorFormView from 'my_react_components';
function App() {
return (
<ConnectorFormView />
);
}
export default App;
Actually that's all. I very recommend to see the attached paper at the beginning of this answer because I maybe skipped very important info for you.

JSX element type [External Module] does not have any construct or call signatures

When trying to run some simple edition of Microsoft Typescript-React starter tutorial: https://github.com/Microsoft/TypeScript-React-Starter I'm getting the following error over an imported module:
(31,30): JSX element type 'SplitPane' does not have any construct or call signatures.
I have checked online, and there are a couple of questions with my same error: What does the error "JSX element type '...' does not have any construct or call signatures" mean? & https://github.com/Microsoft/TypeScript/issues/28631 but they don't help me to understand where my error is, since I am using an external module.
To make it clearer I'll add some code. I am using the same files as are outputed when using npx create-react-app my-app --scripts-version=react-scripts-ts, and just edited package.json, and created SplittedPane.tsx.
This is the file SplittedPane.tsx:
import * as React from "react";
import * as SplitPane from "react-split-pane"
interface Props {
numberPanes: number;
}
interface State {
numberPanes: number;
}
class SplittedPane extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state ={
numberPanes: props.numberPanes || 1
};
}
onIncrement = () => this.updatePanes(this.state.numberPanes + 1);
onDecrement = () => this.updatePanes(this.state.numberPanes - 1);
render() {
if (this.state.numberPanes === 1) {
return (
<div className="globalView">
<button onClick={this.onIncrement}>+</button>
<div className="panesView">
<SplitPane split="horizontal" defaultSize="50%">
<h1>West</h1>
<h1>East</h1>
</SplitPane>
</div>
</div>
)
} else {
throw new Error('The number of Panes is not supported');
}
}
updatePanes(numberPanes: number) {
this.setState({numberPanes});
}
}
export default SplittedPane;
And this the package.json:
{
"name": "react-typescript-github-microsoft-tutorial",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts-ts": "3.1.0",
"styled-components":"^4.2.0",
"react-split-pane":"^0.1.87"
},
"scripts": {
"start": "react-scripts-ts start",
"build": "react-scripts-ts build",
"test": "react-scripts-ts test --env=jsdom",
"eject": "react-scripts-ts eject"
},
"devDependencies": {
"#types/jest": "^24.0.11",
"#types/node": "^11.13.7",
"#types/react": "^16.8.14",
"#types/react-dom": "^16.8.4",
"typescript": "^3.4.5"
}
}
I would expect this code to create two panes (split window), once I include this SplittedPane into the index.tsx file. However, it is crashing with the aforementioned error:
(31,30): JSX element type 'SplitPane' does not have any construct or call signatures.
Since I am using this external module, 'react-split-pane', how can I overcome this issue? The solutions I saw for similar errors seem not to be aimed toward external modules.
Thank you in advance
react-split-pane uses a single named default export, which should be imported like
import SplitPane from "react-split-pane"

react-responsive-accordion -> getting "Uncaught TypeError: Cannot read property 'number' of undefined"

I am new to react js and creating a simple react app to display an accordion by using 'react-responsive-accordion'. followed the link 'https://www.npmjs.com/package/react-responsive-accordion' to do so, but i am getting below the error
Below is the code:
package.json class:
{
"name": "reactapp",
"version": "1.0.0",
"description": "React JS Application",
"scripts": {
"start": "webpack-dev-server"
},
"author": "",
"license": "ISC",
"dependencies": {
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-polyfill": "^6.9.1",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-0": "^6.5.0",
"babel-register": "^6.9.0",
"react": "^16.1.1",
"react-collapsible": "^2.0.3",
"react-dom": "^16.1.1",
"react-responsive-accordion": "^1.0.0",
"webpack": "^1.13.1",
"webpack-dev-middleware": "^1.6.1",
"webpack-dev-server": "^1.14.1",
"webpack-hot-middleware": "^2.11.0"
}
}
main.js class:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(<App />, document.getElementById('app'));
App.jsx class:
import React from 'react';
import Accordion from 'react-responsive-accordion';
class App extends React.Component {
render() {
return (
<div>
<Accordion>
<div data-trigger="A nifty React accordion component">
<p>So this is an Accordion component that used the react-collapsible component. How handy.</p>
</div>
<div data-trigger="What the difference?" data-trigger-when-open="THAT is the difference!">
<p>An Accordion is different to a Collapsible in the sense that only one "tray" will be open at any one time.</p>
</div>
<div data-trigger="I'm responsive and I have a little secret. Look inside.">
<p>And this Accordion component is also completely repsonsive. Hurrah for mobile users!</p>
</div>
</Accordion>
</div>
);
}
}
export default App;
webpack.config.js:
var config = {
entry: './main.js',
output: {
path: '/',
filename: 'index.js'
},
devServer: {
inline: true,
port: 8089
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015','react']
}
},
{
test: /\.scss/,
loader: 'style-loader!css-loader!sass-loader'
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
}
]
}
}
module.exports = config;
Any help to resolve this issue would be appreciated.
Ran into this same problem on another package and decided to downgrade React for now. The problem seems to be caused by a change in React. If you look deeper into the code, it actually complains about:
propTypes: {
transitionTime: _react2.default.PropTypes.number,
This happens because the way to handle PropTypes has changed. Apparently the old way to handle them has been removed in version 16. You can read more here: https://reactjs.org/docs/typechecking-with-proptypes.html
And possible solutions that I can think of:
Use an older version of react (like 15.6.1 in the example given by Nandu Kalidindi).
get the module fixed
Accordion expects an array of children this.props.children with data-trigger according to this https://github.com/glennflanagan/react-responsive-accordion/blob/master/src/Accordion.js#L15.
Modify your render method to something like this so as to provide a valid children props. Or check out this working example WORKING DEMO
render() {
return (
<div>
<Accordion>
<div data-trigger="A nifty React accordion component">
<p>So this is an Accordion component that used the react-collapsible component. How handy.</p>
</div>
<div data-trigger="What is the difference?" data-trigger-when-open={<div><span style={{color: "yellow"}}>THAT</span> is the difference!</div>}>
<p>An Accordion is different to a Collapsible in the sense that only one "tray" will be open at any one time.</p>
</div>
</Accordion>
</div>
);
}
package only works for react 15 per the github repo. Handling of proptypes changed in 16
Even I faced the same issue but it got resolved. The changes were made in package.json only .Below are the changes done. Try to give exact value for all of these below:
"prop-types": "^15.6.2"
"react": "^15.4.0"
"react-dom": "^15.4.0"
Earlier these were 16.x.x .I just changed to a version lower then 15.5.x which I read somewhere is stable.
After this go to the terminal in my case or cmd in windows and go the folder where your react-app is created ie where node_modules, src are and then press
npm install --save
npm start
This may be helpful to some people.
My case was like this, by used React.PropType, I got error 'Uncaught TypeError: Cannot read property 'number' of undefined'
Header.propTypes = {
userid: React.PropType.number
};
Then I changed to use PropTypes from 'prop-types':
import PropTypes from 'prop-types';
...
Header.propTypes = {
userid: PropTypes.number
};
Please refer to https://reactjs.org/docs/typechecking-with-proptypes.html for more details, I am using:
"react": "^16.6.1",
"react-dom": "^16.6.1",
install react-collapsible-react16 for react version 16 and up. This works for me.

How to use Fine Uploader in React js

Hi i am newbie to Reactjs. i am using fineuploader to upload files to the server.i want to create a FineUploader Component ,so that i can use it where ever i want.
package.json
{
"name": "Sample",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"babel-core": "^6.2.1",
"babel-loader": "^6.2.0",
"babel-preset-es2015": "^6.1.18",
"babel-preset-react": "^6.1.18",
"fine-uploader": "^5.7.1",
"react": "^0.14.6",
"react-dom": "^0.14.6",
"webpack": "^1.12.9",
"webpack-dev-server": "^1.14.0"
}
}
webpack.config.js
var path = require("path");
module.exports = {
entry: [
'./Components/Main.js'
],
output:{
path:__dirname,
filename:'bundle.js'
},
resolve: {
alias: {
'fine-uploader': path.resolve('node_modules/fine-uploader/fine-uploader')
}
},
module:{
loaders: [
{
test: /fine-uploader\.js/,
loader: 'exports?qq'
}
],
loaders:[{
test: /\.jsx?$/,
exclude:/node_modules/,
loader:'babel',
query:{
presets: ['react']
}
}]
}
};
FineUploader.js
import React from 'react';
import qq from 'fine-uploader/fine-uploader';
class FineUploader extends React.Component {
constructor (props) {
super(props)
}
componentDidMount () {
const fu = new qq.FineUploaderBasic({
button: this.refs.fu
})
}
render () {
return <div ref='fu'>Upload!</div>
}
}
export default FineUploader;
Main.js
import React from 'react';
import ReactDOM from 'react-dom';
import FineUploader from './FineUploader.js';
var MainContent = React.createClass({
render:function(){
return (
<div>
<FineUploader />
</div>
);
}
});
ReactDOM.render(<MainContent />,document.getElementById('container'));
When i run the Application i am getting the below error
uncaught TypeError: _fineUploader2.default.FineUploaderBasic is not a constructor
i dint no what i am doing wrong ,Plz somebody guide me
To use Fine Uploader with React you will need to create a new uploader instance within the componentDidMount lifecycle method.
Invoked once, only on the client (not on the server), immediately after the initial rendering occurs. At this point in the lifecycle, you can access any refs to your children (e.g., to access the underlying DOM representation).
Fine Uploader needs an actual rendered DOM element for it to attach event handlers, render other DOM elements as children, etc.
Inside of componentDidMount we not only know our component has been created, but we also have a DOM element and we can reference it as such using a ref.
Here's a tiny example component:
class FineUploader extends React.Component {
componentDidMount () {
const fu = new qq.s3.FineUploaderBasic({
button: this.refs.fu
})
}
render () {
return <div ref='fu'>Upload!</div>
}
}
Even though my answer is not completely relevant to the asked question but I would recommend the react-fine-uploader library to make your life easier.
https://github.com/FineUploader/react-fine-uploader

Resources