over_react 2.4.3+dart1 copy "over_react: ^2.4.3+dart1" to clipboard
over_react: ^2.4.3+dart1 copied to clipboard

outdatedDart 1 only

A library for building statically-typed React UI components using Dart.

OverReact #

Pub Documentation [![Join the chat at https://gitter.im/over_react/Lobby](https://badges.gitter.im/over_react/Lobby.svg)](https://gitter.im/over_react/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

Build Status Test Coverage Strong Mode Compliant

A library for building statically-typed React UI components using Dart.


Dart 2 Migration Guide

If you have existing over_react code written on Dart 1 and want to upgrade to Dart 2, please read the Dart 2 Migration Guide


Using it in your project #

If you are not familiar with React JS

Since OverReact is built atop React JS, we strongly encourage you to gain familiarity with it by reading this React JS tutorial first.

  1. Add the over_react package as a dependency in your pubspec.yaml.

    dependencies:
      over_react: ^1.29.0
    
    copied to clipboard
  2. Add the over_react transformer to your pubspec.yaml.

    transformers:
      - over_react
      # Reminder: dart2js should come after any other transformers that touch Dart code
      - $dart2js
    
    copied to clipboard

    Our transformer uses code generation to wire up the different pieces of your component declarations - and to create typed getters/setters for props and state.

  3. Include the native JavaScript react and react_dom libraries in your app’s index.html file, and add an HTML element with a unique identifier where you’ll mount your OverReact UI component(s).

    <html>
      <head>
       <!-- ... -->  
      </head>
      <body>
        <div id="react_mount_point">
          // OverReact component render() output will show up here.
        </div>
    
        <script src="packages/react/react.js"></script>
        <script src="packages/react/react_dom.js"></script>
        <script type="application/dart" src="your_app_name.dart"></script>
        <script src="packages/browser/dart.js"></script>
      </body>
    </html>
    
    copied to clipboard

    Note: When serving your application in production, use packages/react/react_with_react_dom_prod.js file instead of the un-minified react.js / react_dom.js files shown in the example above.

  4. Import the over_react and react_dom libraries into your_app_name.dart, and initialize React within your Dart application. Then build a custom component and mount / render it into the HTML element you created in step 3.

    Be sure to namespace the react_dom.dart import as react_dom to avoid collisions with UiComponent.render when creating custom components.

    import 'dart:html';
    import 'package:over_react/react_dom.dart' as react_dom;
    import 'package:over_react/over_react.dart';
    
    main() {
      // Initialize React within our Dart app
      setClientConfiguration();
    
      // Mount / render your component.
      react_dom.render(Foo()(), querySelector('#react_mount_point'));
    }    
    
    copied to clipboard
  5. Run pub serve in the root of your Dart project.

Running tests in your project #

When running tests on code that uses our transformer (or any code that imports over_react), you must run your tests using Pub.

  1. Add the test/pub_serve transformer to your pubspec.yaml after the over_react transformer.

    transformers:
      - over_react
      - test/pub_serve:
          $include: test/**_test{.*,}.dart
      - $dart2js
    
    copied to clipboard
  2. Use the --pub-serve option when running your tests:

    $ pub run test --pub-serve=8081 test/your_test_file.dart
    
    copied to clipboard

    Note: 8081 is the default port used, but your project may use something different. Be sure to take note of the output when running pub serve to ensure you are using the correct port.

Anatomy of an OverReact component #

If you are not familiar with React JS

Since OverReact is built atop React JS, we strongly encourage you to gain familiarity with it by reading this React JS tutorial first.

The over_react library functions as an additional "layer" atop the Dart react package which handles the underlying JS interop that wraps around React JS.

The library strives to maintain a 1:1 relationship with the React JS component class and API. To do that, an OverReact component is comprised of four core pieces that are each wired up to our Pub transformer using an analogous annotation.

  1. UiFactory
  2. UiProps
  3. UiState (optional)
  4. UiComponent

UiFactory #

UiFactory is a function that returns a new instance of a UiComponent’s UiProps class.

@Factory()
UiFactory<FooProps> Foo;
copied to clipboard

UiProps #

UiProps is a Map class that adds statically-typed getters and setters for each React component prop. It can also be invoked as a function, serving as a builder for its analogous component.

@Props()
class FooProps extends UiProps {
  // ...
}
copied to clipboard

UiProps as a Map

@Factory()
UiFactory<FooProps> Foo;

@Props()
class FooProps extends UiProps {
  String color;
}

@Component()
class FooComponent extends UiComponent<FooProps> {
  // ...  
}

void bar() {
  FooProps props = Foo();

  props.color = '#66cc00';

  print(props.color); // #66cc00
  print(props);       // {FooProps.color: #66cc00}
}

/// You can use the factory to create a UiProps instance
/// backed by an existing Map.
void baz() {
  Map existingMap = {'FooProps.color': '#0094ff'};

  FooProps props = Foo(existingMap);

  print(props.color); // #0094ff
}
copied to clipboard

UiProps as a builder

@Factory()
UiFactory<FooProps> Foo;

@Props()
class FooProps extends UiProps {
  String color;
}

@Component()
class FooComponent extends UiComponent<FooProps> {
  ReactElement bar() {
    // Create a UiProps instance to serve as a builder
    FooProps builder = Foo();

    // Add props
    builder.id = 'the_best_foo';
    builder.color = '#ee2724';

    // Invoke as a function with the desired children
    // to return a new instance of the component.
    return builder('child1', 'child2');
  }

  /// Even better... do it inline! (a.k.a fluent)
  ReactElement baz() {
    return (Foo()
      ..id = 'the_best_foo'
      ..color = 'red'
    )(
      'child1',
      'child2'
    );
  }
}
copied to clipboard

See fluent-style component consumption for more examples on builder usage.

UiState #

UiState is a Map class (just like UiProps) that adds statically-typed getters and setters for each React component state property.

@State()
class FooState extends UiState {
  // ...
}
copied to clipboard

UiState is optional, and won’t be used for every component.

UiComponent #

UiComponent is a subclass of react.Component, containing lifecycle methods and rendering logic for components.

@Component()
class FooComponent extends UiComponent<FooProps> {
  // ...
}
copied to clipboard
  • This component provides statically-typed props via UiProps, as well as utilities for prop forwarding and CSS class merging.
  • The UiStatefulComponent flavor augments UiComponent behavior with statically-typed state via UiState.

Accessing and manipulating props / state within UiComponent

  • Within the UiComponent class, props and state are not just Maps. They are instances of UiProps and UiState, which means you don’t need String keys to access them!
  • newProps() and newState() are also exposed to conveniently create empty instances of UiProps and UiState as needed.
  • typedPropsFactory() and typedStateFactory() are also exposed to conveniently create typed props / state objects out of any provided backing map.
@Component()
class FooComponent extends UiStatefulComponent<FooProps, FooState> {
  @override
  getDefaultProps() => (newProps()
    ..color = '#66cc00'
  );

  @override
  getInitialState() => (newState()
    ..isActive = false
  );

  @override
  componentWillUpdate(Map newProps, Map newState) {
    var tNewState = typedStateFactory(newState);
    var tNewProps = typedPropsFactory(newProps);

    var becameActive = !state.isActive && tNewState.isActive;

    // Do something here!
  }

  @override
  render() {
    return (Dom.div()
      ..style = {
        'color': props.color,
        'fontWeight': state.isActive ? 'bold' : 'normal'
      }
    )(
      (Dom.button()..onClick = _handleButtonClick)('Toggle'),
      props.children
    );
  }

  void _handleButtonClick(SyntheticMouseEvent event) {
    _toggleActive();
  }

  void _toggleActive() {
    setState(newState()
      ..isActive = !state.isActive
    );
  }
}
copied to clipboard

Fluent-style component consumption #

In OverReact, components are consumed by invoking a UiFactory to return a new UiProps builder, which is then modified and invoked to build a ReactElement.

This is done to make "fluent-style" component consumption possible, so that the OverReact consumer experience is very similar to the React JS / "vanilla" react-dart experience.

To demonstrate the similarities, the example below shows a render method for JS, JSX, react-dart, and over_react that will have the exact same HTML markup result.

  • React JS:

    render() {
      return React.createElement('div', {className: 'container'},
        React.createElement('h1', null, 'Click the button!'),
        React.createElement('button', {
          id: 'main_button',
          onClick: _handleClick
        }, 'Click me')
      );
    }
    
    copied to clipboard
  • React JS (JSX):

    render() {
      return <div className="container">
        <h1>Click the button!</h1>
        <button
          id="main_button"
          onClick={_handleClick}
        >Click me</button>
      </div>;
    }
    
    copied to clipboard
  • Vanilla react-dart:

    render() {
      return react.div({'className': 'container'},
        react.h1({}, 'Click the button!'),
        react.button({
          'id': 'main_button',
          'onClick': _handleClick
        }, 'Click me')
      );
    }
    
    copied to clipboard
  • OverReact:

    render() {
      return (Dom.div()..className = 'container')(
        Dom.h1()('Click the button!'),
        (Dom.button()
          ..id = 'main_button'
          ..onClick = _handleClick
        )('Click me')
      );
    }
    
    copied to clipboard

    Let’s break down the OverReact fluent-style shown above

    render() {
      // Create a builder for a <div>,
      // add a CSS class name by cascading a typed setter,
      // and invoke the builder with the HTML DOM <h1> and <button> children.
      return (Dom.div()..className = 'container')(
    
        // Create a builder for an <h1> and invoke it with children.
        // No need for wrapping parentheses, since no props are added.
        Dom.h1()('Click the button!'),
    
        // Create a builder for a <button>,
        (Dom.button()
          // add a ubiquitous DOM prop exposed on all components,
          // which Dom.button() forwards to its rendered DOM,
          ..id = 'main_button'
          // add another prop,
          ..onClick = _handleClick
        // and finally invoke the builder with children.
        )('Click me')
      );
    }
    
    copied to clipboard

DOM components and props #

All react-dart DOM components (react.div, react.a, etc.) have a corresponding Dom method (Dom.div(), Dom.a(), etc.) in OverReact.

ReactElement renderLink() {
  return (Dom.a()
    ..id = 'home_link'
    ..href = '/home'
  )('Home');
}

ReactElement renderResizeHandle() {
  return (Dom.div()
    ..className = 'resize-handle'
    ..onMouseDown = _startDrag
  )();
}
copied to clipboard
  • OverReact DOM components return a new DomProps builder, which can be used to render them via our fluent interface as shown in the examples above.
  • DomProps has statically-typed getters and setters for all "ubiquitous" HTML attribute props.
    • The domProps() function is also available to create a new typed Map or a typed view into an existing Map. Useful for manipulating DOM props and adding DOM props to components that don’t forward them directly.

Component Formatting #

A note on dart_style:

Currently, dart_style (dartfmt) decreases the readability of components built using OverReact's fluent-style. See https://github.com/dart-lang/dart_style/issues/549 for more info.

We're exploring some different ideas to improve automated formatting, but for the time being, we do not recommend using dart_style with OverReact.

However, if you do choose to use dart_style, you can greatly improve its output by using trailing commas in children argument lists:

  • dart_style formatting:
return (Button()
  ..id = 'flip'
  ..skin =
      ButtonSkin.vanilla)((Dom.span()
  ..className = 'flip-container')((Dom.span()..className = 'flipper')(
    (Dom.span()
      ..className =
          'front-side')((Icon()..glyph = IconGlyph.CHEVRON_DOUBLE_RIGHT)()),
    (Dom.span()
      ..className =
          'back-side')((Icon()..glyph = IconGlyph.CHEVRON_DOUBLE_LEFT)()))));
copied to clipboard
  • dart_style formatting, when trailing commas are used:
return (Button()
  ..id = 'flip'
  ..skin = ButtonSkin.vanilla)(
  (Dom.span()..className = 'flip-container')(
    (Dom.span()..className = 'flipper')(
      (Dom.span()..className = 'front-side')(
        (Icon()..glyph = IconGlyph.CHEVRON_DOUBLE_RIGHT)(),
      ),
      (Dom.span()..className = 'back-side')(
        (Icon()..glyph = IconGlyph.CHEVRON_DOUBLE_LEFT)(),
      ),
    ),
  ),
);
copied to clipboard

Guidelines #

To help ensure your OverReact code is readable and consistent, we've arrived at the following formatting rules.

  • ALWAYS place the closing builder parent on a new line.

    Good:

    (Button()
      ..skin = ButtonSkin.SUCCESS
      ..isDisabled = true
    )('Submit')
    
    copied to clipboard

    Bad:

    (Button()
      ..skin = ButtonSkin.SUCCESS
      ..isDisabled = true)('Submit')
    
    copied to clipboard
  • ALWAYS pass component children on a new line with trailing commas and 2 space indentation.

    Good:

    Dom.div()(
      Dom.span()('nested component'),
    )
    
    copied to clipboard
    Dom.div()(
      Dom.span()('nested component A'),
      Dom.span()('nested component B'),
    )
    
    copied to clipboard

    Bad:

    // Children are not on a new line; in most cases,
    // this makes it difficult to quickly determine nesting.
    Dom.div()(Dom.span()('nested component'), Dom.span()('nested component'))
    
    copied to clipboard
    // With nested hierarchies, continuation indents can quickly result
    // in a "pyramid of Doom"
    Dom.div()(
        Dom.ul()(
            Dom.li()(
                Dom.a()('A link!')
            )
        )
    )
    
    copied to clipboard
    // Omitting trailing commas makes it a pain to rearrange lines
    Dom.div()(
      Dom.span()('nested component A'),
      Dom.span()('nested component B')
    )
    Dom.div()(
      Dom.span()('nested component B') // ugh, need to add a comma here...
      Dom.span()('nested component A'),
    )
    
    copied to clipboard
  • AVOID passing children within lists; lists should only be used when the number/order of the children are dynamic.

    Good:

    Dom.div()(
      Dom.span()('nested component'),
      Dom.span()('nested component'),
    )
    
    copied to clipboard
    var children = [
      Dom.div()('List of Items:'),
    ]..addAll(props.items.map(renderItem));
    
    return Dom.div()(children)
    
    copied to clipboard

    Bad:

    Dom.div()([
      (Dom.span()..key = 'span1')('nested component'),
      (Dom.span()..key = 'span2')('nested component'),
    ])
    
    copied to clipboard
  • AVOID specifying more than one cascading prop setter on the same line.

    Good:

    (Dom.div()
      ..id = 'my_div'
      ..className = 'my-class'
    )()
    
    copied to clipboard

    Bad:

    (Dom.div()..id = 'my_div'..className = 'my-class')()
    
    copied to clipboard

Building custom components #

Now that we’ve gone over how to use the over_react package in your project, the anatomy of a component and the DOM components that you get for free from OverReact, you're ready to start building your own custom React UI components.

  1. Start with one of the component boilerplate templates below.
  1. Fill in your props and rendering/lifecycle logic.

  2. Consume your component with the fluent interface.

  3. Run the app you’ve set up to consume over_react

    $ pub serve
    
    copied to clipboard

    That’s it! Code will be automatically generated on the fly by Pub!

Check out some custom component demos to get a feel for what’s possible!

Component Boilerplate Templates #

  • Dart 1 and Dart 2 Backwards Compatible VS Code and WebStorm/IntelliJ Snippets

  • Component Boilerplate

    import 'package:over_react/over_react.dart';
    
    @Factory()
    UiFactory<FooProps> Foo;
    
    @Props()
    class FooProps extends UiProps {
      // Props go here, declared as fields:
      bool isDisabled;
      Iterable<String> items;
    }
    
    @Component()
    class FooComponent extends UiComponent<FooProps> {
      @override
      Map getDefaultProps() => (newProps()
        // Cascade default props here
        ..isDisabled = false
        ..items = []
      );
    
      @override
      render() {
        // Return the rendered component contents here.
        // The `props` variable is typed; no need for string keys!
      }
    }
    
    copied to clipboard
  • Stateful Component Boilerplate

    import 'dart:html';
    import 'package:over_react/over_react.dart';
    
    @Factory()
    UiFactory<BarProps> Bar;
    
    @Props()
    class BarProps extends UiProps {
      // Props go here, declared as fields:
      bool isDisabled;
      Iterable<String> items;
    }
    
    @State()
    class BarState extends UiState {
      // State goes here, declared as fields:
      bool isShown;
    }
    
    @Component()
    class BarComponent extends UiStatefulComponent<BarProps, BarState> {
      @override
      Map getDefaultProps() => (newProps()
        // Cascade default props here
        ..isDisabled = false
        ..items = []
      );
    
      @override
      Map getInitialState() => (newState()
        // Cascade initial state here
        ..isShown = true
      );
    
      @override
      render() {
        // Return the rendered component contents here.
        // The `props` variable is typed; no need for string keys!
      }
    }
    
    copied to clipboard
  • Flux Component Boilerplate

    import 'dart:html';
    import 'package:over_react/over_react.dart';
    
    @Factory()
    UiFactory<BazProps> Baz;
    
    @Props()
    class BazProps extends FluxUiProps<BazActions, BazStore> {
      // Props go here, declared as fields.
      // `actions` and `store` are already defined for you!
    }
    
    @Component()
    class BazComponent extends FluxUiComponent<BazProps> {
      getDefaultProps() => (newProps()
        // Cascade default props here
      );
    
      @override
      render() {
        // Return the rendered component contents here.
        // The `props` variables is typed; no need for string keys!
        // E.g., `props.actions`, `props.store`.
      }
    }
    
    copied to clipboard
  • Stateful Flux Component Boilerplate

    import 'dart:html';
    import 'package:over_react/over_react.dart';
    
    @Factory()
    UiFactory<BazProps> Baz;
    
    @Props()
    class BazProps extends FluxUiProps<BazActions, BazStore> {
      // Props go here, declared as fields.
      // `actions` and `store` are already defined for you!
    }
    
    @State()
    class BazState extends UiState {
      // State goes here, declared as fields.
    }
    
    @Component()
    class BazComponent extends FluxUiStatefulComponent<BazProps, BazState> {
      getDefaultProps() => (newProps()
        // Cascade default props here
      );
    
      @override
      Map getInitialState() => (newState()
        // Cascade initial state here
      );
    
      @override
      render() {
        // Return the rendered component contents here.
        // The `props` variables is typed; no need for string keys!
        // E.g., `props.actions`, `props.store`.
      }
    }
    
    copied to clipboard

Component Best Practices #

  • ALWAYS write informative comments for your component factories. Include what the component relates to, relies on, or if it extends another component.

    Good:

    /// Use the `DropdownButton` component to render a button
    /// that controls the visibility of a child [DropdownMenu].
    ///
    /// * Related to [Button].
    /// * Extends [DropdownTrigger].
    /// * Similar to [SplitButton].
    ///
    /// See: <https://link-to-any-relevant-documentation>.
    @Factory()
    UiFactory<DropdownButtonProps> DropdownButton;
    
    copied to clipboard

    Bad:

    /// Component Factory for a dropdown button component.
    @Factory()
    UiFactory<DropdownButtonProps> DropdownButton;
    
    copied to clipboard

  • ALWAYS set a default / initial value for props / state fields, and document that value in a comment.

    Why? Without default prop values for bool fields, they could be null - which is extremely confusing and can lead to a lot of unnecessary null-checking in your business logic.

    Good:

    @Props()
    DropdownButtonProps extends UiProps {
      /// Whether the [DropdownButton] appears disabled.
      ///
      /// Default: `false`
      bool isDisabled;
    
      /// Whether the [DropdownButton]'s child [DropdownMenu] is open
      /// when the component is first mounted.
      ///
      /// Determines the initial value of [DropdownButtonState.isOpen].
      ///
      /// Default: `false`
      bool initiallyOpen;
    }
    
    @State()
    DropdownButtonState extends UiState {
      /// Whether the [DropdownButton]'s child [DropdownMenu] is open.
      ///
      /// Initial: [DropdownButtonProps.initiallyOpen]
      bool isOpen;
    }
    
    @Component()
    DropdownButtonComponent
        extends UiStatefulComponent<DropdownButtonProps, DropdownButtonState> {
      @override
      Map getDefaultProps() => (newProps()
        ..isDisabled = false
        ..initiallyOpen = false
      );
    
      @override
      Map getInitialState() => (newState()
        ..isOpen = props.initiallyOpen
      );
    }
    
    copied to clipboard

    Bad:

    @Props()
    DropdownButtonProps extends UiProps {
      bool isDisabled;
      bool initiallyOpen;
    }
    
    @State()
    DropdownButtonState extends UiState {
      bool isOpen;
    }
    
    @Component()
    DropdownButtonComponent
        extends UiStatefulComponent<DropdownButtonProps, DropdownButtonState> {
      // Confusing stuff is gonna happen in here with
      // bool props that could be null.
    }
    
    copied to clipboard

  • AVOID adding props or state fields that don't have an informative comment.

    Good:

    @Props()
    DropdownButtonProps extends UiProps {
      /// Whether the [DropdownButton] appears disabled.
      ///
      /// Default: `false`
      bool isDisabled;
    
      /// Whether the [DropdownButton]'s child [DropdownMenu] is open
      /// when the component is first mounted.
      ///
      /// Determines the initial value of [DropdownButtonState.isOpen].
      ///
      /// Default: `false`
      bool initiallyOpen;
    }
    
    @State()
    DropdownButtonState extends UiState {
      /// Whether the [DropdownButton]'s child [DropdownMenu] is open.
      ///
      /// Initial: [DropdownButtonProps.initiallyOpen]
      bool isOpen;
    }
    
    copied to clipboard

    Bad:

    @Props()
    DropdownButtonProps extends UiProps {
      bool isDisabled;
      bool initiallyOpen;
    }
    
    @State()
    DropdownButtonState extends UiState {
      bool isOpen;
    }
    
    copied to clipboard

Common Pitfalls #

Below you’ll find some common errors / issues that new consumers run into when building custom components.

Don’t see the issue you're having? Tell us about it.


null object does not have a method 'call'

ⓧ Exception: The null object does not have a method 'call'.
copied to clipboard

This error is thrown when you call a @Factory() function that has not been initialized due to the over_react transformer not running, you’ll get this error.

Make sure you’ve followed the setup instructions.


404 on .dart file #

ⓧ GET http://localhost:8080/src/your_component.dart
ⓧ An error occurred loading file: http://localhost:8080/src/your_component.dart
copied to clipboard

When the over_react transformer finds something wrong with your file, it logs an error in Pub and causes the invalid file to 404. This ensures that when the transformer breaks, pub build will break, and you’ll know about it.

Check your pub serve output for errors.

Contributing #

Yes please! (Please read our contributor guidelines first)

Versioning #

The over_react library adheres to Semantic Versioning:

  • Any API changes that are not backwards compatible will bump the major version (and reset the minor / patch).
  • Any new functionality that is added in a backwards-compatible manner will bump the minor version (and reset the patch).
  • Any backwards-compatible bug fixes that are added will bump the patch version.
37
likes
0
points
200k
downloads

Publisher

verified publisherworkiva.com

Weekly Downloads

2024.06.20 - 2025.01.02

A library for building statically-typed React UI components using Dart.

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

analyzer, barback, built_redux, built_value, js, logging, meta, path, platform_detect, quiver, react, source_span, transformer_utils, w_common, w_flux

More

Packages that depend on over_react