- Working with React props
- Defining React components
- 2.1 Stateless Components
- 2.2 Statefull Components
- Rendering React Elements
- 3.1 Java type limitations
- 3.2 Creating Factory methods
- Java 8 lambda quirks
- Working around usages of function binding in javascript
- Creating a javascript bundle of 3rd party components
gwt-react builds on many of the features offered by gwt-interop-utils. Please familiarise yourself with this first by reading the supplied documentation
React props in gwt-react are defined by the BaseProps
class that itself extends JsPlainObj
.
Typically you will want to create a subclass that defines the properties for your component.
This will give your the advantage of strong typing e.g.
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
static class TodoItemProps extends BaseProps {
TodoModel.Todo todo;
boolean isEditing;
JsBiConsumer<TodoModel.Todo, String> doSave;
JsBiConsumer<TodoList.Action, TodoModel.Todo> doAction;
}
There will however be some situations where you cannot always use strong typing. For example,
some components will push additional props down to their children. In this case, you will probably
want to use the typeless access capabilities provided by JsPlainObj
to access these properties.
gwt-react supports two types of React components, statefull and stateless. Stateful components are implemented as classes that derive from
Component<PROPS,STATE>
. Stateless components are implemented as functions/lamdas that match the
StatelessComponent<PROPS>
functional interface. You should ideally try and make as
many of your components stateless functions as possible.
Stateless components can be defined using lambdas e.g.
public class SomeClass {
public static StatelessComponent<BaseProps> someComponent = props ->
div(null,
span(null, "Some text")
);
}
//This component could then be used as follows:
React.createElement(SomeClass.someComponent, null);
Alternatively, you can use method references that match the StatelessComponent functional interface instead e.g.
public class SomeClass {
public static DOMElement<HtmlProps> someComponent(BaseProps props) {
return div(null,
span(null, "Some text")
);
};
}
//This component could then be used as follows:
React.createElement(SomeClass::someComponent, null);
You can define as many stateless functional components within a given class as you want. Typically using method references will result in slightly less code being generated by the GWT compiler.
Statefull components are created by subclassing either Component<PROPS,STATE>
or
PureComponent<PROPS,STATE>
e.g.
@JsType
class StatefulExample extends Component<StatefulExample.Props, StatefulExample.State> {
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name="Object")
static class Props extends BaseProps {
String aProp;
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name="Object")
static class State extends JsPlainObj {
String aStateVar;
}
public StatefulExample(StatefulExample.Props props) {
super(props);
this.state = new State();
this.state.aStateVar = "Some initial value";
}
public ReactElement render() {
//Return some react elements rendered based on this.props and this.state
return div(null);
}
//Optional lifecycle methods, add as required
public void componentWillMount() {
}
public void componentDidMount() {
}
public void componentWillReceiveProps(Props nextProps) {
}
public boolean shouldComponentUpdate(Props nextProps, State nextState) {
return true;
}
public void componentWillUpdate(Props nextProps, State nextState) {
}
public void componentDidUpdate(Props prevProps, State prevState) {
}
public void componentWillUnmount() {
}
}
To use one of these components, pass it's class into React.createElement
e.g.
//The above StatefulExample component could then be used as follows:
StatefulExample.Props props = new StatefulExample.Props();
props.aProp = "Some value";
React.createElement(StatefulExample.class, props);
The majority of javascript React code you will see uses something called JSX. This is just a preprocessor that allows you to write React component hierarchies in HTML style e.g.
var rootElement =
(<div>
<h1>Contacts</h1>
<ul>
<li>
<h2>James Nelson</h2>
<a href="mailto:[email protected]">[email protected]</a>
</li>
</ul>
</div>);
The JSX preprocessor compiles the above down to a bunch of React.createElement
calls e.g.
var rootElement =
React.createElement('div', {},
React.createElement('h1', {}, "Contacts"),
React.createElement('ul', {},
React.createElement('li', {},
React.createElement('h2', {}, "Some One"),
React.createElement('a', {href: 'mailto:[email protected]'}, '[email protected]')
)
)
)
A good introduction to this more traditional createElement approach can be found here: Learn Raw React — no JSX, no Flux, no ES6, no Webpack…. Also check out the Official React documentation.
gwt-react uses this React.createElement
approach. The above example would be written in Java as follows:
DOMElement<HtmlProps> rootElement =
React.createElement("div", null,
React.createElement("h1", null, "Contacts"),
React.createElement("ul", null,
React.createElement("li", null,
React.createElement("h2", null, "Some One"),
React.createElement("a", new AnchorProps().href("mailto:[email protected]"), "[email protected]")
)
)
);
Alternatively, you can use the shorthand React.DOM.xxx
methods for the common HTML elements e.g.
import static gwt.react.client.api.React.DOM.*;
DOMElement<HtmlProps> rootElement =
div(null,
h1(null, "Contacts"),
ul(null,
li(null,
h2(null, "Some One),
a(new AnchorProps().href("mailto:someone@somecompany.com"), "someone@somecompany.com")
)
)
);
There were a few situations where all the possible combinations of parameters to create elements
couldn't be represented in Java. The first example is passing child props. In this case, you have to
bypass the type system by using the GwtReact.castAsReactElement
method e.g.
div(null,
div(null, "There are " + countChildren + " child components"),
castAsReactElement(props.children),
br(null)
);
The second situation is where you want to pass an array of elements e.g.
Array<ReactElement> newChildren = React.Children.map(<some function>)
div(null,
castAsReactElement(newChildren)
);
The final situation is passing string literals instead of elements. In this case you
can use the GwtReact.stringLiteral
method e.g.
p(null,
stringLiteral("Clicked: " + props.getInt("value") + " times "),
button(new BtnProps().onClick(getOnIncrementFnProp()), "+"),
stringLiteral(" "),
button(new BtnProps().onClick(getOnDecrementFnProp()), "-"),
stringLiteral(" "),
button(new BtnProps().onClick(this::incrementIfOdd), "Increment if odd"),
stringLiteral(" "),
button(new BtnProps().onClick(this::incrementAsync), "Increment async")
);
Unfortunately you cannot use JSX syntax in Java. However, you can create simple factory methods to make your code more readable and provide better type checking
public class Todo {
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
public static class Props extends BaseProps {
//Some properties
}
private static StatelessComponent<Props> component = (props) ->
li(new HtmlProps()
.style(new CssProps()
.textDecoration(props.completed ? "line-through" : "none"))
.onClick((e) -> props.onClickToToggle.call()),
props.text);
static ReactElement<Props, ?> todo(Props props) { return React.createElement(component, props); }
}
// You can then use the slightly more readable factory method to instantiate one of these components as follows:
import static <base_package>.Todo.todo;
Todo.Props someProps = new Todo.Props();
//Set props appropriately
ReactElement newElement = todo(someProps);
Depending on the component, a factory method can also provide a nice way of explicitly passing additional information such as text values or child elements e.g.
static ReactElement<Props, ?> someButtonComp(ButtonProps props, String btnText) { return React.createElement(component, props, btnText); }
static ReactElement<Props, ?> someComponentThatTakesASingleChild(FormProps props, ReactElement<?,?> child) { return React.createElement(component, props, child); }
static ReactElement<Props, ?> someComponentThatTakesASpecificChild(FormProps props, ButtonComponent child) { return React.createElement(component, props, child); }
static ReactElement<Props, ?> someComponentThatTakesManyChildren(FormProps props, ReactElement ...children) { return React.createElement(component, props, children); }
There are a few quirks with using Java 8 lambdas. For example, you cannot assign a lambda directly to a variable or parameter of type Object. This is because the compiler cannot infer the type of Functional Interface. This has implications when using typeless properties e.g.
// The following won't compile
JsPlainObj someProps = $jsPlainObj("someCallback", () -> {<some code>));
//Instead you will have to create a temporary variable
JsProcedure someCallback = () -> { < some code> };
JsPlainObj someProps = $jsPlainObj("someCallback", someCallback);
In javascript, the concept of what this
actually refers to is very nebulous and in may cases can be modified. In addition,
you can dynamically create a function by taking an existing function and adding arguments. Both these cases are typically achieved
by using the [bind method](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind). Java
doesn't have the same concepts, so when translating some examples you have to work around the limitation. For example, the todomvc
example passed a set of functions down to each todoitem that had the todo model element bound as an extra argument e.g.
handleSave: function (todoToSave, text) {
.
}
var todoItems = shownTodos.map(function (todo) {
return (
<TodoItem
key={todo.id}
todo={todo}
onSave={this.handleSave.bind(this, todo)}
/>
);
}, this);
//In the TodoItem component the code just called the props.onSave and the todo item was
//automatically added as an extra argument
handleSubmit: function (event) {
this.props.onSave(val);
}
Converting this to Java, we have to pass the todo from the other direction
private void handleSave(TodoModel.Todo todoToSave, String text) {
..
}
Array<ReactElement> todoItems = shownTodos.map((todo, index, theArray) -> {
TodoItem.TodoItemProps todoProps = new TodoItem.TodoItemProps();
todoProps.key = todo.id;
todoProps.todo = todo;
todoProps.doSave = this::handleSave;
return React.createElement(TodoItem.component, todoProps);
});
//In the TodoItem.component we call the props.doSave function and pass the todo from its props
private void submitTodo(FocusEvent event) {
.
props.doSave.call(props.todo, val);
.
.
}
Most React related libraries and components are published on npm.
Some of the projects will provide a UMD build that exposes their API on the global window object. However, many don't. The gwt-react project provides an example of how you can use the node package manager and webpack to build a single bundle of Javascript you can use with gwt-react and its related projects.
-
First install node 4.4.x from nodejs.org.
-
Edit gwt-react/dist/src/index.js Comment out any of the imports/window assignments for any of the libaries you don't need. Add any additional libaries your application will be using.
-
If you are adding additional libraries, edit gwt-react/dist/package.json and add the dependencies to the dependencies section.
-
Open a command line and cd to gwt-react/dist. Then type
npm install
This will download all the required packages. -
Type
npm run build
to produce a development bundle. The Javascript you need to include in your application will be output to gwt-react/dist/lib/gwt-react-bundle.js -
Type
npm run build:min
to produce a production minified bundle. The Javascript you need to include in your application will be output to gwt-react/dist/lib/gwt-react-bundle.min.js
Under gwt-react/dist/lib you will find additional bundles that include common combinations of projects offered by GWTReact