Normal React App will look like this in index.js
// index.js ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
You need to make following changes.
// index.js export function renderComponent(selector) { ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById(selector) ); } module.exports = { renderComponent: renderComponent, }; window.renderComponent= renderComponent;
Simply build js file (bundle.js etc…) and put it to your GWT project.
For SnmpManager, put it in snmpmanager/web-app/js
Load JavaScript file in gsp header before GWT.
In constructor of GWT Component (such as Panel), add some child component with distinctive ID.
// ReactPanel.java public class ReactPanel extends VerticalPanel { public ReactPanel() { Label label = new Label(); label.getElement().setId("react-hostTree-root"); add(label); } }
In the same class, override onLoad() method and create native method which calls renderComponent() function of React App you just made above.
// ReactPanel.java @Override protected void onLoad() { super.onLoad(); renderHostTreeReact("react-hostTree-root"); } public native void renderHostTreeReact(String containerId) /*-{ $wnd.renderComponent(containerId); }-*/;
You need to put render method in onLoad() method otherwise React complains there is no such element with ID “react-hostTree-root”. GWT doesn't necessary show all component in the browser when constructor is called.
This is all the work you need to put React App in GWT.
If your React app with Redux, nothing shouldn't be different. Everything should work in the same way as you run ReactRedux App with nodeJS.
We are going to need to create GwtHolder.js. This class allows React Application to call function (native method) in GWT.
In index.js of ReactRedux App, add following codes.
// index.js import * as bitcoinActions from './actions/bitcoinActions'; export function fetchBitcoinPrice(code) { store.dispatch(bitcoinActions.fetchBitcoinPrice(code)) } module.exports = { fetchBitcoinPrice: fetchBitcoinPrice, renderComponent: renderComponent, }; window.fetchBitcoinPrice= fetchBitcoinPrice; window.renderComponent= renderComponent;
Above code basically exporting function that dispatch redux action.
By that, we can call fetchBitcoinPrice(code) function from GWT native method as follows.
// ReactPanel.java public native void reactAction() /*-{ $wnd.fetchBitcoinPrice("EUR"); }-*/;
From GWT code, we can call reactAction() method just like other JAVA method in GWT.
We create JavaScript class (GwtHolder.js) in React App to interface React and GWT.
// GwtHolder.js let gwtComp let rateCodeFunc; class GwtHolder { constructor() { } static setGwtComp(comp) { gwtComp = comp; } static setRateCodeFunc(func) { rateCodeFunc = func; } static rateCodeFunc(rate, code) { if (rateCodeFunc === undefined) { console.error("rateCodeFunc is undefined"); } else { console.log("calling rateCodeFunc"); console.log(rate + " and " + code); rateCodeFunc(gwtComp, rate, code); } } } export default GwtHolder
Feature of GwtHolder class is
React App can all GWT native method through GwtHolder as shown below.
import GwtHolder from '../GwtHolder'; GwtHolder.rateCodeFunc(rate, code);
Export GwtHolder in index.js so that GWT can see it. Later GWT will inject component and functions into GwtHolder.js
// index.js module.exports = { fetchBitcoinPrice: fetchBitcoinPrice, renderComponent: renderComponent, GwtHolder: GwtHolder, }; window.fetchBitcoinPrice= fetchBitcoinPrice; window.renderComponent= renderComponent; window.GwtHolder= GwtHolder;
In GWT, write native method (JavaScript function) to be passed to GwtHolder and JAVA method which actually do some work with GWT.
// ReactPanel.java public native void price(ReactPanel reactPanel, String price, String code) /*-{ var message = "Price given to GWT from React is " + price + " and code is " + code; console.log(message); console.log(reactPanel); reactPanel.@com.errigal.ems.client.dashboard.panel.ReactPanel::gwtAnnounce(Ljava/lang/String;)(message); }-*/; public void gwtAnnounce(String message) { WindowDialog.notify(message); }
So above code, native method price() will be passed to GwtHolder.js and bridge JavaScript and GWT.
We need to accept GWT Component instance as one of parameters. GWT Component means the class where all Java Code we write is written. In this case ReactPanel.
The reason is once price() native method is passed to React side, “this” keyword cannot be used because “this” means React App, not GWT component.
gwtAnnounce() method is normal Java method. In this case, it will show popup with message in GWT app.
So sequence of calling GWT method from React App will be
Now we have function (native method) to pass to React. So all we need to do is actually pass it on to React. We can do so by adding extra code in renderReact method.
// ReactPanel.java public native void renderHostTreeReact(String containerId) /*-{ var reactPanel = this; var func = reactPanel.@com.errigal.ems.client.dashboard.layouts.ReactPanel::price(Lcom/errigal/ems/client/dashboard/layouts/ReactPanel;Ljava/lang/String;Ljava/lang/String;); $wnd.renderComponent(containerId); $wnd.GwtHolder.setGwtComp(reactPanel); $wnd.GwtHolder.setRateCodeFunc(func); }-*/;
If you write GWT native method to be given to React App without argument “ReactPanel reactPanel” like below,
// malfunction - ReactPanel.java public native void price(String price, String code) /*-{ var message = "Price given to GWT from React is " + price + " and code is " + code; console.log(message); console.log(this); this.@com.errigal.ems.client.dashboard.panel.ReactPanel::gwtAnnounce(Ljava/lang/String;)(message); }-*/; public void gwtAnnounce(String message) { WindowDialog.notify(message); }
First, IntelliJ won't give you any error because syntax are perfect. IntelliJ recognise this.~~~.gwtAnnounce() method exists and executable from native method.
But when it actually executed from React, browser console tells you “gwtAnnounce is not function” or “no such property gwtAnnounce”.
If you have look at console log before the error, you'll see javascript object “Window” is outputted. That is React App object.
This is why you have to take GWT component itself (For this page's example, “ReactPanel reactPanel”) as one of arguments.
Using bind method like below but no avail.
// malfunction - ReactPanel.java public native void renderHostTreeReact(String containerId) /*-{ var reactPanel = this; var func = reactPanel.@com.errigal.ems.client.dashboard.layouts.ReactPanel::price(Ljava/lang/String;Ljava/lang/String;); func.bind(this); $wnd.renderComponent(containerId); $wnd.GwtHolder.setRateCodeFunc(func); }-*/;
import GwtHolder from '../GwtHolder'; GwtHolder.getGwtComp().price(rate, code);
This will give you “Uncaught TypeError: a.price is not a function” in browser console log.
I suspect we need to take some extra step to call GWT native method directly from javascript.