Skip to content

Make adjustments necessary to support strong-mode compliance for over_react #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
97 changes: 68 additions & 29 deletions lib/react.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,56 @@
/// A Dart library for building UI using ReactJS.
library react;

/// Top-level ReactJS [Component class](https://facebook.github.io/react/docs/top-level-api.html#react.component)
/// which provides the [ReactJS Component API](https://facebook.github.io/react/docs/component-api.html)
import 'package:react/src/typedefs.dart';

/// Top-level ReactJS [Component class](https://facebook.github.io/react/docs/react-component.html)
/// which provides the [ReactJS Component API](https://facebook.github.io/react/docs/react-component.html#reference)
abstract class Component {
/// ReactJS `Component` props.
Map props;
/// A private field that backs [props], which is exposed via getter/setter so
/// it can be overridden in strong mode.
///
/// Necessary since the `@virtual` annotation within the meta package
/// [doesn't work for overriding fields](https://github.com/dart-lang/sdk/issues/27452).
///
/// TODO: Switch back to a plain field once this issue is fixed.
Map _props;

/// A private field that backs [state], which is exposed via getter/setter so
/// it can be overridden in strong mode.
///
/// Necessary since the `@virtual` annotation within the meta package
/// [doesn't work for overriding fields](https://github.com/dart-lang/sdk/issues/27452).
///
/// TODO: Switch back to a plain field once this issue is fixed.
Map _state = {};

/// Provides access to the underlying DOM representation of the [render]ed `Component`.
dynamic ref;
/// A private field that backs [ref], which is exposed via getter/setter so
/// it can be overridden in strong mode.
///
/// Necessary since the `@virtual` annotation within the meta package
/// [doesn't work for overriding fields](https://github.com/dart-lang/sdk/issues/27452).
///
/// TODO: Switch back to a plain field once this issue is fixed.
Ref _ref;

/// ReactJS [Component] props.
///
/// Related: [state]
Map get props => _props;
set props(Map value) => _props = value;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As opposed to making these getters/setters, can we just use the @virtual annotation instead? (See meta package 1.0.4 changelog)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greglittlefield-wf I just tried that, and I still get the following errors in over_react:

ERROR: Field declaration Component.ref cannot be overridden in UiComponent. ([over_react] lib/src/component_declaration/component_base.dart:106)
ERROR: Field declaration Component.props cannot be overridden in UiComponent. ([over_react] lib/src/component_declaration/component_base.dart:179)
ERROR: Field declaration Component.state cannot be overridden in UiStatefulComponent. ([over_react] lib/src/component_declaration/component_base.dart:230)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you will need to be using Dart 1.19.1.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually just tried this, still does not work. I found this issue dart-lang/sdk#27452, which makes it seem like strong mode does not allow you to override fields, even with the @virtual annotation. I think that may just be for overriding fields with other fields.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's too bad. Can we add a code comment, then, explaining that this is for strong mode compliance and adding a TODO linked to that SDK issue?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greglittlefield-wf good idea.


/// ReactJS [Component] state.
///
/// Related: [props]
Map get state => _state;
set state(Map value) => _state = value;

/// A function that returns a component reference:
///
/// * [Component] if it is a Dart component.
/// * `Element` _(DOM node)_ if it is a React DOM component.
Ref get ref => _ref;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These getters should have doc comments, since consumers can't see the doc comments of private fields.

What about this:

/// A private field that backs [ref], which is exposed via getter/setter so
/// it can be overridden in strong mode.
///
/// Necessary since the `@virtual` annotation within the meta package
/// [doesn't work for overriding fields](https://github.com/dart-lang/sdk/issues/27452).
///
/// TODO: Switch back to a plain field once this issue is fixed.
Ref _ref;

...

/// A function that returns a component reference:
///
/// * [Component] if it is a Dart component.
/// * `Element` _(DOM node)_ if it is a React DOM component.
Ref get ref => _ref;
set ref(Ref value) => _ref = value;

set ref(Ref value) => _ref = value;

dynamic _jsRedraw;

Expand All @@ -32,14 +74,14 @@ abstract class Component {
/// instance of this `Component` returned by [render].
dynamic get jsThis => _jsThis;

/// Allows the [ReactJS `displayName` property](https://facebook.github.io/react/docs/component-specs.html#displayname)
/// Allows the [ReactJS `displayName` property](https://facebook.github.io/react/docs/react-component.html#displayname)
/// to be set for debugging purposes.
String get displayName => runtimeType.toString();

/// Bind the value of input to [state[key]].
bind(key) => [state[key], (value) => setState({key: value})];

initComponentInternal(props, _jsRedraw, [ref, _jsThis]) {
initComponentInternal(props, _jsRedraw, [Ref ref, _jsThis]) {
this._jsRedraw = _jsRedraw;
this.ref = ref;
this._jsThis = _jsThis;
Expand All @@ -57,9 +99,6 @@ abstract class Component {
transferComponentState();
}

/// ReactJS `Component` state.
Map state = {};

/// Private reference to the value of [state] from the previous render cycle.
///
/// Useful for ReactJS lifecycle methods [shouldComponentUpdate], [componentWillUpdate] and [componentDidUpdate].
Expand Down Expand Up @@ -98,7 +137,7 @@ abstract class Component {
///
/// Optionally accepts a callback that gets called after the component updates.
///
/// [A.k.a "forceUpdate"](https://facebook.github.io/react/docs/component-api.html#forceupdate)
/// [A.k.a "forceUpdate"](https://facebook.github.io/react/docs/react-component.html#forceupdate)
void redraw([callback()]) {
setState({}, callback);
}
Expand All @@ -109,7 +148,7 @@ abstract class Component {
///
/// Also allows [newState] to be used as a transactional `setState` callback.
///
/// See: <https://facebook.github.io/react/docs/component-api.html#setstate>
/// See: <https://facebook.github.io/react/docs/react-component.html#setstate>
void setState(dynamic newState, [callback()]) {
if (newState is Map) {
_nextState.addAll(newState);
Expand All @@ -128,7 +167,7 @@ abstract class Component {
///
/// Optionally accepts a callback that gets called after the component updates.
///
/// See: <https://facebook.github.io/react/docs/component-api.html#replacestate>
/// See: <https://facebook.github.io/react/docs/react-component.html#setstate>
void replaceState(Map newState, [callback()]) {
Map nextState = newState == null ? {} : new Map.from(newState);
_nextState = nextState;
Expand All @@ -143,7 +182,7 @@ abstract class Component {
/// If you call [setState] within this method, [render] will see the updated state and will be executed only once
/// despite the [state] value change.
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#mounting-componentwillmount>
/// See: <https://facebook.github.io/react/docs/react-component.html#mounting-componentwillmount>
void componentWillMount() {}

/// ReactJS lifecycle method that is invoked once, only on the client _(not on the server)_, immediately after the
Expand All @@ -153,7 +192,7 @@ abstract class Component {
///
/// The [componentDidMount] method of child `Component`s is invoked _before_ that of parent `Component`.
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount>
/// See: <https://facebook.github.io/react/docs/react-component.html#mounting-componentdidmount>
void componentDidMount() {}

/// ReactJS lifecycle method that is invoked when a `Component` is receiving [newProps].
Expand All @@ -165,16 +204,16 @@ abstract class Component {
///
/// Calling [setState] within this function will not trigger an additional [render].
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops>
void componentWillReceiveProps(newProps) {}
/// See: <https://facebook.github.io/react/docs/react-component.html#updating-componentwillreceiveprops>
void componentWillReceiveProps(Map newProps) {}

/// ReactJS lifecycle method that is invoked before rendering when [nextProps] or [nextState] are being received.
///
/// Use this as an opportunity to return false when you're certain that the transition to the new props and state
/// will not require a component update.
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate>
bool shouldComponentUpdate(nextProps, nextState) => true;
/// See: <https://facebook.github.io/react/docs/react-component.html#updating-shouldcomponentupdate>
bool shouldComponentUpdate(Map nextProps, Map nextState) => true;

/// ReactJS lifecycle method that is invoked immediately before rendering when [nextProps] or [nextState] are being
/// received.
Expand All @@ -183,8 +222,8 @@ abstract class Component {
///
/// Use this as an opportunity to perform preparation before an update occurs.
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate>
void componentWillUpdate(nextProps, nextState) {}
/// See: <https://facebook.github.io/react/docs/react-component.html#updating-componentwillupdate>
void componentWillUpdate(Map nextProps, Map nextState) {}

/// ReactJS lifecycle method that is invoked immediately after the `Component`'s updates are flushed to the DOM.
///
Expand All @@ -193,20 +232,20 @@ abstract class Component {
/// Use this as an opportunity to operate on the [rootNode] (DOM) when the `Component` has been updated as a result
/// of the values of [prevProps] / [prevState].
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate>
void componentDidUpdate(prevProps, prevState) {}
/// See: <https://facebook.github.io/react/docs/react-component.html#updating-componentdidupdate>
void componentDidUpdate(Map prevProps, Map prevState) {}

/// ReactJS lifecycle method that is invoked immediately before a `Component` is unmounted from the DOM.
///
/// Perform any necessary cleanup in this method, such as invalidating timers or cleaning up any DOM [Element]s that
/// were created in [componentDidMount].
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#unmounting-componentwillunmount>
/// See: <https://facebook.github.io/react/docs/react-component.html#unmounting-componentwillunmount>
void componentWillUnmount() {}

/// Invoked once before the `Component` is mounted. The return value will be used as the initial value of [state].
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#getinitialstate>
/// See: <https://facebook.github.io/react/docs/react-component.html#getinitialstate>
Map getInitialState() => {};

/// Invoked once and cached when [reactComponentClass] is called. Values in the mapping will be set on [props]
Expand All @@ -215,7 +254,7 @@ abstract class Component {
/// This method is invoked before any instances are created and thus cannot rely on [props]. In addition, be aware
/// that any complex objects returned by `getDefaultProps` will be shared across instances, not copied.
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#getdefaultprops>
/// See: <https://facebook.github.io/react/docs/react-component.html#getdefaultprops>
Map getDefaultProps() => {};

/// __Required.__
Expand All @@ -224,13 +263,13 @@ abstract class Component {
/// be either a virtual representation of a native DOM component (such as [DivElement]) or another composite
/// `Component` that you've defined yourself.
///
/// See: <https://facebook.github.io/react/docs/component-specs.html#render>
/// See: <https://facebook.github.io/react/docs/react-component.html#render>
dynamic render();
}

/// Typedef of a transactional [Component.setState] callback.
///
/// See: <https://facebook.github.io/react/docs/component-api.html#setstate>
/// See: <https://facebook.github.io/react/docs/react-component.html#setstate>
typedef Map _TransactionalSetStateCallback(Map prevState, Map props);


Expand Down
3 changes: 2 additions & 1 deletion lib/react_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:react/react_client/react_interop.dart';
import "package:react/react_dom.dart";
import "package:react/react_dom_server.dart";
import "package:react/src/react_client/synthetic_event_wrappers.dart" as events;
import 'package:react/src/typedefs.dart';

export 'package:react/react_client/react_interop.dart' show ReactElement, ReactJsComponentFactory;

Expand Down Expand Up @@ -167,7 +168,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() {
}
};

var getRef = (name) {
Ref getRef = (name) {
var ref = getProperty(jsThis.refs, name);
if (ref == null) return null;
if (ref is Element) return ref;
Expand Down
3 changes: 2 additions & 1 deletion lib/react_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
library react_test;

import 'package:react/react.dart';
import 'package:react/src/typedefs.dart';

class MarkupDescription {
final String tag;
Expand All @@ -29,7 +30,7 @@ _reactDom(String name) {
};
}

initializeComponent(Component component, [Map props = const {}, List children, redraw, ref]) {
initializeComponent(Component component, [Map props = const {}, List children, redraw, Ref ref]) {
if (redraw == null) redraw = () {};
var extendedProps = new Map.from(component.getDefaultProps())
..addAll(props);
Expand Down
7 changes: 7 additions & 0 deletions lib/src/typedefs.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
library react.typedefs;

/// Typedef for `react.Component.ref`, which should return one of the following specified by the provided [ref]:
///
/// * `react.Component` if it is a Dart component.
/// * `Element` _(DOM node)_ if it is a React DOM component.
typedef dynamic Ref(String ref);
5 changes: 4 additions & 1 deletion test/child_key_warning_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ int _nextFactoryId = 0;
///
/// This prevents React JS from not printing key warnings it deems as "duplicates".
void renderWithUniqueOwnerName(ReactElement render()) {
ReactDartComponentFactoryProxy factory = react.registerComponent(() => new _OwnerHelperComponent());
final factory =
react.registerComponent(() => new _OwnerHelperComponent()) as ReactDartComponentFactoryProxy;
factory.reactClass.displayName = 'OwnerHelperComponent_$_nextFactoryId';
_nextFactoryId++;

Expand Down Expand Up @@ -128,9 +129,11 @@ void main() {

Function CustomComponent = react.registerComponent(() => new _CustomComponent());
class _CustomComponent extends react.Component {
@override
render() => react.div({}, []);
}

class _OwnerHelperComponent extends react.Component {
@override
render() => props['render']();
}