Skip to content

Latest commit

 

History

History
395 lines (298 loc) · 14.7 KB

DOCUMENTATION.md

File metadata and controls

395 lines (298 loc) · 14.7 KB

Contents

  1. Working with React props
  2. Defining React components
    • 2.1 Stateless Components
    • 2.2 Statefull Components
  3. Rendering React Elements
    • 3.1 Java type limitations
    • 3.2 Creating Factory methods
  4. Java 8 lambda quirks
  5. Working around usages of function binding in javascript
  6. Creating a javascript bundle of 3rd party components

1. Working with React props

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.

2. Defining React components

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.

2.1 Stateless Components

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.

2.2 Stateful Components

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);

3. Rendering React Elements

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")
                )
            )
        );

3.1 Java type limitations

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")
    );

3.2 Creating Factory methods

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); }

4. Java 8 Lambda quirks

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);

5. Working around usages of function binding in javascript

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);
        .
        .
    }

6. Creating a javascript bundle of 3rd party components

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.

  1. First install node 4.4.x from nodejs.org.

  2. 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.

  3. If you are adding additional libraries, edit gwt-react/dist/package.json and add the dependencies to the dependencies section.

  4. Open a command line and cd to gwt-react/dist. Then type npm install This will download all the required packages.

  5. 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

  6. 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