From d2e4beb9c77d736d87bf793137ad907e784fe1d5 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 26 Oct 2017 15:57:06 -0700 Subject: [PATCH 01/31] Port over initial stab at context --- example/test/context_test.dart | 18 +++++++ example/test/context_test.html | 14 +++++ example/test/react_test_components.dart | 38 ++++++++++++++ js_src/dart_helpers.js | 41 ++++++++++++--- lib/react.dart | 18 ++++++- lib/react.js | 41 ++++++++++++--- lib/react_client.dart | 69 +++++++++++++++++++++---- lib/react_client/react_interop.dart | 38 ++++++++++++-- lib/react_prod.js | 41 ++++++++++++--- lib/react_with_addons.js | 41 ++++++++++++--- lib/react_with_react_dom_prod.js | 41 ++++++++++++--- 11 files changed, 343 insertions(+), 57 deletions(-) create mode 100644 example/test/context_test.dart create mode 100644 example/test/context_test.html diff --git a/example/test/context_test.dart b/example/test/context_test.dart new file mode 100644 index 00000000..2b7673f1 --- /dev/null +++ b/example/test/context_test.dart @@ -0,0 +1,18 @@ +import 'dart:html'; + +import 'package:react/react_dom.dart' as react_dom; +import 'package:react/react_client.dart'; + +import 'react_test_components.dart'; + +void main() { + setClientConfiguration(); + + react_dom.render(contextComponent({}, + contextConsumerComponent({}), + ), querySelector('#content')); + + react_dom.render(contextComponent({}, + contextConsumerComponent({}), + ), querySelector('#content')); +} diff --git a/example/test/context_test.html b/example/test/context_test.html new file mode 100644 index 00000000..216fde50 --- /dev/null +++ b/example/test/context_test.html @@ -0,0 +1,14 @@ + + + + + context_test + + +
+ + + + + + diff --git a/example/test/react_test_components.dart b/example/test/react_test_components.dart index d1efb9ff..c377bf4c 100644 --- a/example/test/react_test_components.dart +++ b/example/test/react_test_components.dart @@ -152,3 +152,41 @@ class _MainComponent extends react.Component { } var mainComponent = react.registerComponent(() => new _MainComponent()); + +class _ContextComponent extends react.Component { + int _renderCount = 0; + + @override + Map getChildContext() => { + 'foo': 'bar', + 'renderCount': _renderCount + }; + + @override + Iterable get childContextKeys => const ['foo', 'renderCount']; + + render() { + _renderCount++; + + return react.ul({}, + 'ContextComponent.getChildContext(): ', + getChildContext().toString(), + props['children'] + ); + } +} +var contextComponent = react.registerComponent(() => new _ContextComponent()); + + +class _ContextConsumerComponent extends react.Component { + @override + Iterable get contextKeys => const ['foo', 'renderCount']; + + render() { + return react.ul({}, + 'ContextConsumerComponent.context: ', + context.toString(), + ); + } +} +var contextConsumerComponent = react.registerComponent(() => new _ContextConsumerComponent()); diff --git a/js_src/dart_helpers.js b/js_src/dart_helpers.js index 45939220..e9f24ff4 100644 --- a/js_src/dart_helpers.js +++ b/js_src/dart_helpers.js @@ -6,7 +6,32 @@ function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { + var childContextTypes; + var contextTypes; + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + childContextTypes[childContextKeys[i]] = React.PropTypes.any; + } + } + + if (contextKeys && contextKeys.length !== 0) { + contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + contextTypes[contextKeys[i]] = React.PropTypes.any; + } + } + return { + getChildContext: function() { + return dartInteropStatics.getChildContext(this.props.internal); + }, + childContextTypes: childContextTypes, + contextTypes: contextTypes, getInitialState: function() { this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); return {}; @@ -17,17 +42,17 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentDidMount: function() { dartInteropStatics.handleComponentDidMount(this.dartComponent); }, - componentWillReceiveProps: function(nextProps) { - dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal); + componentWillReceiveProps: function(nextProps, nextContext) { + dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal, nextContext); }, - shouldComponentUpdate: function(nextProps, nextState) { - return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent); + shouldComponentUpdate: function(nextProps, nextState, nextContext) { + return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent, nextContext); }, - componentWillUpdate: function(nextProps, nextState) { - dartInteropStatics.handleComponentWillUpdate(this.dartComponent); + componentWillUpdate: function(nextProps, nextState, nextContext) { + dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); + componentDidUpdate: function(prevProps, prevState, prevContext) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); diff --git a/lib/react.dart b/lib/react.dart index e64b4808..09be85b8 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -10,6 +10,8 @@ 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 { + Map _context; + /// A private field that backs [props], which is exposed via getter/setter so /// it can be overridden in strong mode. /// @@ -37,6 +39,9 @@ abstract class Component { /// TODO: Switch back to a plain field once this issue is fixed. Ref _ref; + Map get context => _context; + set context(Map value) => _context = value; + /// ReactJS [Component] props. /// /// Related: [state] @@ -81,13 +86,18 @@ abstract class Component { /// Bind the value of input to [state[key]]. bind(key) => [state[key], (value) => setState({key: value})]; - initComponentInternal(props, _jsRedraw, [Ref ref, _jsThis]) { + initComponentInternal(context, props, _jsRedraw, [Ref ref, _jsThis]) { this._jsRedraw = _jsRedraw; this.ref = ref; this._jsThis = _jsThis; + _initContext(context); _initProps(props); } + _initContext(context) { + _context = new Map.from(context ?? {}); + } + _initProps(props) { this.props = new Map.from(props); this.nextProps = this.props; @@ -243,6 +253,12 @@ abstract class Component { /// See: void componentWillUnmount() {} + Map getChildContext() => {}; + + Iterable get childContextKeys => const []; + + Iterable get contextKeys => const []; + /// Invoked once before the `Component` is mounted. The return value will be used as the initial value of [state]. /// /// See: diff --git a/lib/react.js b/lib/react.js index 855306e4..7de53d01 100644 --- a/lib/react.js +++ b/lib/react.js @@ -4325,7 +4325,32 @@ function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { + var childContextTypes; + var contextTypes; + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + childContextTypes[childContextKeys[i]] = React.PropTypes.any; + } + } + + if (contextKeys && contextKeys.length !== 0) { + contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + contextTypes[contextKeys[i]] = React.PropTypes.any; + } + } + return { + getChildContext: function() { + return dartInteropStatics.getChildContext(this.props.internal); + }, + childContextTypes: childContextTypes, + contextTypes: contextTypes, getInitialState: function() { this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); return {}; @@ -4336,17 +4361,17 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentDidMount: function() { dartInteropStatics.handleComponentDidMount(this.dartComponent); }, - componentWillReceiveProps: function(nextProps) { - dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal); + componentWillReceiveProps: function(nextProps, nextContext) { + dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal, nextContext); }, - shouldComponentUpdate: function(nextProps, nextState) { - return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent); + shouldComponentUpdate: function(nextProps, nextState, nextContext) { + return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent, nextContext); }, - componentWillUpdate: function(nextProps, nextState) { - dartInteropStatics.handleComponentWillUpdate(this.dartComponent); + componentWillUpdate: function(nextProps, nextState, nextContext) { + dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); + componentDidUpdate: function(prevProps, prevState, prevContext) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); diff --git a/lib/react_client.dart b/lib/react_client.dart index 30c0e561..11805b71 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -11,6 +11,7 @@ import "dart:html"; import 'dart:js'; import "package:js/js.dart"; +import "package:js/js_util.dart" show newObject; import "package:react/react.dart"; import "package:react/react_client/js_interop_helpers.dart"; import 'package:react/react_client/react_interop.dart'; @@ -178,6 +179,24 @@ dynamic _convertArgsToChildren(List childrenArgs) { } } +@JS('Object.keys') +external List _objectKeys(Object object); + +_jsifyContext(Map context) { + var interopContext = newObject(); + context.forEach((key, value) { + setProperty(interopContext, key, value); + }); + + return interopContext; +} + +Map _unjsifyContext(interopContext) { + return new Map.fromIterable(_objectKeys(interopContext), value: (key) { + return getProperty(interopContext, key); + }); +} + /// The static methods that proxy JS component lifecycle methods to Dart components. final ReactDartInteropStatics _dartInteropStatics = (() { var zone = Zone.current; @@ -197,7 +216,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }; Component component = componentStatics.componentFactory() - ..initComponentInternal(internal.props, jsRedraw, getRef, jsThis) + ..initComponentInternal(_unjsifyContext(jsThis.context), internal.props, jsRedraw, getRef, jsThis) ..initStateInternal(); // Return the component so that the JS proxying component can store it, @@ -205,6 +224,21 @@ final ReactDartInteropStatics _dartInteropStatics = (() { return component; }); + InteropContext getChildContext(Component component) => zone.run(() { + var childContext = component.getChildContext(); + + assert(() { + var undeclaredKeys = childContext.keys.toSet().difference(component.childContextKeys.toSet()); + if (undeclaredKeys.isNotEmpty) { + throw new Exception('Context keys used in `getChildContext()` but not declared in `childContextKeys`: $undeclaredKeys'); + } + + return true; + }); + + return _jsifyContext(childContext); + }); + /// Wrapper for [Component.componentWillMount]. void handleComponentWillMount(Component component) => zone.run(() { component @@ -225,13 +259,19 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// 1. Update [Component.props] using the value stored to [Component.nextProps] /// in `componentWillReceiveProps`. /// 2. Update [Component.state] by calling [Component.transferComponentState] - void _afterPropsChange(Component component) { + /// 3. Update [Component.context] with the latest + void _afterPropsChange(Component component, InteropContext nextContext) { component.props = component.nextProps; // [1] component.transferComponentState(); // [2] + // [3] + component.context = _unjsifyContext(component.jsThis); } void _clearPrevState(Component component) { component.prevState = null; + + // [4] + component.context = _unjsifyContext(context); } void _callSetStateCallbacks(Component component) { @@ -248,7 +288,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { } /// Wrapper for [Component.componentWillReceiveProps]. - void handleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal) => zone.run(() { + void handleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, nextContext) => zone.run(() { var nextProps = _getNextProps(component, nextInternal); component ..nextProps = nextProps @@ -256,14 +296,14 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }); /// Wrapper for [Component.shouldComponentUpdate]. - bool handleShouldComponentUpdate(Component component) => zone.run(() { + bool handleShouldComponentUpdate(Component component, nextContext) => zone.run(() { _callSetStateTransactionalCallbacks(component); if (component.shouldComponentUpdate(component.nextProps, component.nextState)) { return true; } else { // If component should not update, update props / transfer state because componentWillUpdate will not be called. - _afterPropsChange(component); + _afterPropsChange(component, nextContext); _callSetStateCallbacks(component); // Clear out prevState after it's done being used so it's not retained _clearPrevState(component); @@ -272,15 +312,15 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }); /// Wrapper for [Component.componentWillUpdate]. - void handleComponentWillUpdate(Component component) => zone.run(() { + void handleComponentWillUpdate(Component component, nextContext) => zone.run(() { component.componentWillUpdate(component.nextProps, component.nextState); - _afterPropsChange(component); + _afterPropsChange(component, nextContext); }); /// Wrapper for [Component.componentDidUpdate]. /// /// Uses [prevState] which was transferred from [Component.nextState] in [componentWillUpdate]. - void handleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal) => zone.run(() { + void handleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, prevContext) => zone.run(() { var prevInternalProps = prevInternal.props; component.componentDidUpdate(prevInternalProps, component.prevState); _callSetStateCallbacks(component); @@ -304,6 +344,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { return new ReactDartInteropStatics( initComponent: allowInterop(initComponent), + getChildContext: allowInterop(getChildContext), handleComponentWillMount: allowInterop(handleComponentWillMount), handleComponentDidMount: allowInterop(handleComponentDidMount), handleComponentWillReceiveProps: allowInterop(handleComponentWillReceiveProps), @@ -318,18 +359,24 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// Returns a new [ReactComponentFactory] which produces a new JS /// [`ReactClass` component class](https://facebook.github.io/react/docs/top-level-api.html#react.createclass). ReactDartComponentFactoryProxy _registerComponent(ComponentFactory componentFactory, [Iterable skipMethods = const []]) { + var componentInstance = componentFactory(); var componentStatics = new ComponentStatics(componentFactory); + var jsConfig = new JsComponentConfig( + childContextKeys: componentInstance.childContextKeys, + contextKeys: componentInstance.contextKeys, + ); + /// Create the JS [`ReactClass` component class](https://facebook.github.io/react/docs/top-level-api.html#react.createclass) /// with custom JS lifecycle methods. var reactComponentClass = React.createClass( - createReactDartComponentClassConfig(_dartInteropStatics, componentStatics) - ..displayName = componentFactory().displayName + createReactDartComponentClassConfig(_dartInteropStatics, componentStatics, jsConfig) + ..displayName = componentInstance.displayName ); // Cache default props and store them on the ReactClass so they can be used // by ReactDartComponentFactoryProxy and externally. - final Map defaultProps = new Map.unmodifiable(componentFactory().getDefaultProps()); + final Map defaultProps = new Map.unmodifiable(componentInstance.getDefaultProps()); reactComponentClass.dartDefaultProps = defaultProps; return new ReactDartComponentFactoryProxy(reactComponentClass); diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index b26188ab..1c0f910a 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -83,6 +83,8 @@ class ReactClassConfig { Function componentWillUpdate, Function componentDidUpdate, Function componentWillUnmount, + Function getChildContext, + Map childContextTypes, Function getDefaultProps, Function getInitialState, Function render @@ -122,6 +124,8 @@ class ReactElement { /// For composite components (react-dart or pure JS), this will be a [ReactClass]. external dynamic get type; + external InteropContext get context; + /// The props this element was created with. external InteropProps get props; @@ -146,6 +150,7 @@ class ReactElement { @anonymous class ReactComponent { external Component get dartComponent; + external InteropContext get context; external InteropProps get props; external get refs; external void setState(state, [callback]); @@ -158,6 +163,10 @@ class ReactComponent { // Interop internals // ---------------------------------------------------------------------------- +@JS() +@anonymous +class InteropContext {} + /// A JavaScript interop class representing a React JS `props` object. /// /// Used for storing/accessing [ReactDartComponentInternal] objects in @@ -183,6 +192,8 @@ class InteropProps { /// /// __For internal/advanced use only.__ class ReactDartComponentInternal { + Map context; + /// For a `ReactElement`, this is the initial props with defaults merged. /// /// For a `ReactComponent`, this is the props the component was last rendered with, @@ -213,15 +224,20 @@ void markChildrenValidated(List children) { /// [dartInteropStatics] and [componentStatics] internally to proxy between /// the JS and Dart component instances. @JS('_createReactDartComponentClassConfig') -external ReactClassConfig createReactDartComponentClassConfig(ReactDartInteropStatics dartInteropStatics, ComponentStatics componentStatics); +external ReactClassConfig createReactDartComponentClassConfig( + ReactDartInteropStatics dartInteropStatics, + ComponentStatics componentStatics, + [JsComponentConfig jsConfig] +); typedef Component _InitComponent(ReactComponent jsThis, ReactDartComponentInternal internal, ComponentStatics componentStatics); +typedef InteropContext _GetChildContext(Component component); typedef void _HandleComponentWillMount(Component component); typedef void _HandleComponentDidMount(Component component); -typedef void _HandleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal); -typedef bool _HandleShouldComponentUpdate(Component component); -typedef void _HandleComponentWillUpdate(Component component); -typedef void _HandleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal); +typedef void _HandleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, InteropContext nextContext); +typedef bool _HandleShouldComponentUpdate(Component component, InteropContext nextContext); +typedef void _HandleComponentWillUpdate(Component component, InteropContext nextContext); +typedef void _HandleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, InteropContext prevContext); typedef void _HandleComponentWillUnmount(Component component); typedef dynamic _HandleRender(Component component); @@ -231,6 +247,7 @@ typedef dynamic _HandleRender(Component component); class ReactDartInteropStatics { external factory ReactDartInteropStatics({ _InitComponent initComponent, + _GetChildContext getChildContext, _HandleComponentWillMount handleComponentWillMount, _HandleComponentDidMount handleComponentDidMount, _HandleComponentWillReceiveProps handleComponentWillReceiveProps, @@ -253,3 +270,14 @@ class ComponentStatics { ComponentStatics(this.componentFactory); } + +/// Additional configuration passed to [createReactDartComponentClassConfig] +/// that needs to be directly accessible by that JS code. +@JS() +@anonymous +class JsComponentConfig { + external factory JsComponentConfig({ + Iterable childContextKeys, + Iterable contextKeys, + }); +} diff --git a/lib/react_prod.js b/lib/react_prod.js index 2ce98061..a831a5e7 100644 --- a/lib/react_prod.js +++ b/lib/react_prod.js @@ -17,7 +17,32 @@ function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { + var childContextTypes; + var contextTypes; + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + childContextTypes[childContextKeys[i]] = React.PropTypes.any; + } + } + + if (contextKeys && contextKeys.length !== 0) { + contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + contextTypes[contextKeys[i]] = React.PropTypes.any; + } + } + return { + getChildContext: function() { + return dartInteropStatics.getChildContext(this.props.internal); + }, + childContextTypes: childContextTypes, + contextTypes: contextTypes, getInitialState: function() { this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); return {}; @@ -28,17 +53,17 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentDidMount: function() { dartInteropStatics.handleComponentDidMount(this.dartComponent); }, - componentWillReceiveProps: function(nextProps) { - dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal); + componentWillReceiveProps: function(nextProps, nextContext) { + dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal, nextContext); }, - shouldComponentUpdate: function(nextProps, nextState) { - return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent); + shouldComponentUpdate: function(nextProps, nextState, nextContext) { + return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent, nextContext); }, - componentWillUpdate: function(nextProps, nextState) { - dartInteropStatics.handleComponentWillUpdate(this.dartComponent); + componentWillUpdate: function(nextProps, nextState, nextContext) { + dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); + componentDidUpdate: function(prevProps, prevState, prevContext) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); diff --git a/lib/react_with_addons.js b/lib/react_with_addons.js index ef122fab..4e821374 100644 --- a/lib/react_with_addons.js +++ b/lib/react_with_addons.js @@ -5966,7 +5966,32 @@ function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { + var childContextTypes; + var contextTypes; + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + childContextTypes[childContextKeys[i]] = React.PropTypes.any; + } + } + + if (contextKeys && contextKeys.length !== 0) { + contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + contextTypes[contextKeys[i]] = React.PropTypes.any; + } + } + return { + getChildContext: function() { + return dartInteropStatics.getChildContext(this.props.internal); + }, + childContextTypes: childContextTypes, + contextTypes: contextTypes, getInitialState: function() { this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); return {}; @@ -5977,17 +6002,17 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentDidMount: function() { dartInteropStatics.handleComponentDidMount(this.dartComponent); }, - componentWillReceiveProps: function(nextProps) { - dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal); + componentWillReceiveProps: function(nextProps, nextContext) { + dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal, nextContext); }, - shouldComponentUpdate: function(nextProps, nextState) { - return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent); + shouldComponentUpdate: function(nextProps, nextState, nextContext) { + return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent, nextContext); }, - componentWillUpdate: function(nextProps, nextState) { - dartInteropStatics.handleComponentWillUpdate(this.dartComponent); + componentWillUpdate: function(nextProps, nextState, nextContext) { + dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); + componentDidUpdate: function(prevProps, prevState, prevContext) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); diff --git a/lib/react_with_react_dom_prod.js b/lib/react_with_react_dom_prod.js index 82e9ddd3..595bb0e7 100644 --- a/lib/react_with_react_dom_prod.js +++ b/lib/react_with_react_dom_prod.js @@ -17,7 +17,32 @@ function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { + var childContextTypes; + var contextTypes; + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + childContextTypes[childContextKeys[i]] = React.PropTypes.any; + } + } + + if (contextKeys && contextKeys.length !== 0) { + contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + contextTypes[contextKeys[i]] = React.PropTypes.any; + } + } + return { + getChildContext: function() { + return dartInteropStatics.getChildContext(this.props.internal); + }, + childContextTypes: childContextTypes, + contextTypes: contextTypes, getInitialState: function() { this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); return {}; @@ -28,17 +53,17 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentDidMount: function() { dartInteropStatics.handleComponentDidMount(this.dartComponent); }, - componentWillReceiveProps: function(nextProps) { - dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal); + componentWillReceiveProps: function(nextProps, nextContext) { + dartInteropStatics.handleComponentWillReceiveProps(this.dartComponent, nextProps.internal, nextContext); }, - shouldComponentUpdate: function(nextProps, nextState) { - return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent); + shouldComponentUpdate: function(nextProps, nextState, nextContext) { + return dartInteropStatics.handleShouldComponentUpdate(this.dartComponent, nextContext); }, - componentWillUpdate: function(nextProps, nextState) { - dartInteropStatics.handleComponentWillUpdate(this.dartComponent); + componentWillUpdate: function(nextProps, nextState, nextContext) { + dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); + componentDidUpdate: function(prevProps, prevState, prevContext) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); From 3ec943613d7a5f32a3131c63fe0f5cff40f0ad7d Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 26 Oct 2017 15:57:33 -0700 Subject: [PATCH 02/31] Fix breaking change to initComponentInternal --- lib/react.dart | 2 +- lib/react_client.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index 09be85b8..6f861537 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -86,7 +86,7 @@ abstract class Component { /// Bind the value of input to [state[key]]. bind(key) => [state[key], (value) => setState({key: value})]; - initComponentInternal(context, props, _jsRedraw, [Ref ref, _jsThis]) { + initComponentInternal(props, _jsRedraw, [Ref ref, _jsThis, context]) { this._jsRedraw = _jsRedraw; this.ref = ref; this._jsThis = _jsThis; diff --git a/lib/react_client.dart b/lib/react_client.dart index 11805b71..8493dd4d 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -216,7 +216,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }; Component component = componentStatics.componentFactory() - ..initComponentInternal(_unjsifyContext(jsThis.context), internal.props, jsRedraw, getRef, jsThis) + ..initComponentInternal(internal.props, jsRedraw, getRef, jsThis, _unjsifyContext(jsThis.context)) ..initStateInternal(); // Return the component so that the JS proxying component can store it, From e1a44b0801e498471fc7aba29d710a014626625f Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 27 Oct 2017 08:50:44 -0700 Subject: [PATCH 03/31] Use internal pattern for individual context keys --- js_src/dart_helpers.js | 2 +- lib/react_client.dart | 43 ++++++++++------------------- lib/react_client/react_interop.dart | 16 +++++++---- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/js_src/dart_helpers.js b/js_src/dart_helpers.js index e9f24ff4..33310fcb 100644 --- a/js_src/dart_helpers.js +++ b/js_src/dart_helpers.js @@ -33,7 +33,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati childContextTypes: childContextTypes, contextTypes: contextTypes, getInitialState: function() { - this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); + this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, this.context, componentStatics); return {}; }, componentWillMount: function() { diff --git a/lib/react_client.dart b/lib/react_client.dart index 8493dd4d..b4048278 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -11,7 +11,6 @@ import "dart:html"; import 'dart:js'; import "package:js/js.dart"; -import "package:js/js_util.dart" show newObject; import "package:react/react.dart"; import "package:react/react_client/js_interop_helpers.dart"; import 'package:react/react_client/react_interop.dart'; @@ -182,18 +181,20 @@ dynamic _convertArgsToChildren(List childrenArgs) { @JS('Object.keys') external List _objectKeys(Object object); -_jsifyContext(Map context) { - var interopContext = newObject(); +InteropContext _jsifyContext(Map context) { + var interopContext = new InteropContext(); context.forEach((key, value) { - setProperty(interopContext, key, value); + setProperty(interopContext, key, new ReactDartContextInternal(value)); }); return interopContext; } -Map _unjsifyContext(interopContext) { +Map _unjsifyContext(InteropContext interopContext) { + // TODO consider using `contextKeys` for this if perf of objectKeys is bad. return new Map.fromIterable(_objectKeys(interopContext), value: (key) { - return getProperty(interopContext, key); + ReactDartContextInternal internal = getProperty(interopContext, key); + return internal.value; }); } @@ -202,7 +203,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { var zone = Zone.current; /// Wrapper for [Component.getInitialState]. - Component initComponent(ReactComponent jsThis, ReactDartComponentInternal internal, ComponentStatics componentStatics) => zone.run(() { + Component initComponent(ReactComponent jsThis, ReactDartComponentInternal internal, InteropContext context, ComponentStatics componentStatics) => zone.run(() { void jsRedraw() { jsThis.setState(emptyJsMap); } @@ -216,7 +217,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }; Component component = componentStatics.componentFactory() - ..initComponentInternal(internal.props, jsRedraw, getRef, jsThis, _unjsifyContext(jsThis.context)) + ..initComponentInternal(internal.props, jsRedraw, getRef, jsThis, _unjsifyContext(context)) ..initStateInternal(); // Return the component so that the JS proxying component can store it, @@ -225,18 +226,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }); InteropContext getChildContext(Component component) => zone.run(() { - var childContext = component.getChildContext(); - - assert(() { - var undeclaredKeys = childContext.keys.toSet().difference(component.childContextKeys.toSet()); - if (undeclaredKeys.isNotEmpty) { - throw new Exception('Context keys used in `getChildContext()` but not declared in `childContextKeys`: $undeclaredKeys'); - } - - return true; - }); - - return _jsifyContext(childContext); + return _jsifyContext(component.getChildContext()); }); /// Wrapper for [Component.componentWillMount]. @@ -264,14 +254,11 @@ final ReactDartInteropStatics _dartInteropStatics = (() { component.props = component.nextProps; // [1] component.transferComponentState(); // [2] // [3] - component.context = _unjsifyContext(component.jsThis); + component.context = _unjsifyContext(nextContext); } void _clearPrevState(Component component) { component.prevState = null; - - // [4] - component.context = _unjsifyContext(context); } void _callSetStateCallbacks(Component component) { @@ -288,7 +275,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { } /// Wrapper for [Component.componentWillReceiveProps]. - void handleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, nextContext) => zone.run(() { + void handleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, InteropContext nextContext) => zone.run(() { var nextProps = _getNextProps(component, nextInternal); component ..nextProps = nextProps @@ -296,7 +283,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }); /// Wrapper for [Component.shouldComponentUpdate]. - bool handleShouldComponentUpdate(Component component, nextContext) => zone.run(() { + bool handleShouldComponentUpdate(Component component, InteropContext nextContext) => zone.run(() { _callSetStateTransactionalCallbacks(component); if (component.shouldComponentUpdate(component.nextProps, component.nextState)) { @@ -312,7 +299,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }); /// Wrapper for [Component.componentWillUpdate]. - void handleComponentWillUpdate(Component component, nextContext) => zone.run(() { + void handleComponentWillUpdate(Component component, InteropContext nextContext) => zone.run(() { component.componentWillUpdate(component.nextProps, component.nextState); _afterPropsChange(component, nextContext); }); @@ -320,7 +307,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// Wrapper for [Component.componentDidUpdate]. /// /// Uses [prevState] which was transferred from [Component.nextState] in [componentWillUpdate]. - void handleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, prevContext) => zone.run(() { + void handleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, InteropContext prevContext) => zone.run(() { var prevInternalProps = prevInternal.props; component.componentDidUpdate(prevInternalProps, component.prevState); _callSetStateCallbacks(component); diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index 1c0f910a..71ad989f 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -124,8 +124,6 @@ class ReactElement { /// For composite components (react-dart or pure JS), this will be a [ReactClass]. external dynamic get type; - external InteropContext get context; - /// The props this element was created with. external InteropProps get props; @@ -165,7 +163,9 @@ class ReactComponent { @JS() @anonymous -class InteropContext {} +class InteropContext { + external factory InteropContext(); +} /// A JavaScript interop class representing a React JS `props` object. /// @@ -192,8 +192,6 @@ class InteropProps { /// /// __For internal/advanced use only.__ class ReactDartComponentInternal { - Map context; - /// For a `ReactElement`, this is the initial props with defaults merged. /// /// For a `ReactComponent`, this is the props the component was last rendered with, @@ -201,6 +199,12 @@ class ReactDartComponentInternal { Map props; } +class ReactDartContextInternal { + final dynamic value; + + ReactDartContextInternal(this.value); +} + /// Marks [child] as validated, as if it were passed into [React.createElement] /// as a variadic child. /// @@ -230,7 +234,7 @@ external ReactClassConfig createReactDartComponentClassConfig( [JsComponentConfig jsConfig] ); -typedef Component _InitComponent(ReactComponent jsThis, ReactDartComponentInternal internal, ComponentStatics componentStatics); +typedef Component _InitComponent(ReactComponent jsThis, ReactDartComponentInternal internal, InteropContext context, ComponentStatics componentStatics); typedef InteropContext _GetChildContext(Component component); typedef void _HandleComponentWillMount(Component component); typedef void _HandleComponentDidMount(Component component); From 27fea39f29160c0b103307a2fa8dfa1027ef2902 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 27 Oct 2017 08:55:44 -0700 Subject: [PATCH 04/31] Fix JS, conditionally declare context pieces on config --- js_src/dart_helpers.js | 57 ++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/js_src/dart_helpers.js b/js_src/dart_helpers.js index 33310fcb..14fdb3b9 100644 --- a/js_src/dart_helpers.js +++ b/js_src/dart_helpers.js @@ -5,33 +5,8 @@ function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } -function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { - var childContextTypes; - var contextTypes; - - var childContextKeys = jsConfig && jsConfig.childContextKeys; - var contextKeys = jsConfig && jsConfig.contextKeys; - - if (childContextKeys && childContextKeys.length !== 0) { - childContextTypes = {}; - for (var i = 0; i < childContextKeys.length; i++) { - childContextTypes[childContextKeys[i]] = React.PropTypes.any; - } - } - - if (contextKeys && contextKeys.length !== 0) { - contextTypes = {}; - for (var i = 0; i < contextKeys.length; i++) { - contextTypes[contextKeys[i]] = React.PropTypes.any; - } - } - - return { - getChildContext: function() { - return dartInteropStatics.getChildContext(this.props.internal); - }, - childContextTypes: childContextTypes, - contextTypes: contextTypes, +function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics, jsConfig) { + var config = { getInitialState: function() { this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, this.context, componentStatics); return {}; @@ -61,6 +36,34 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati return dartInteropStatics.handleRender(this.dartComponent); } }; + + // React limits the accessible context entries + // to the keys specified in childContextTypes/contextTypes. + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + config.childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; + } + + // Only declare this when `hasChildContext` is true to avoid unnecessarily + // creating interop context objects for components that won't use it. + config.getChildContext = function() { + return dartInteropStatics.getChildContext(this.dartComponent); + }; + } + + if (contextKeys && contextKeys.length !== 0) { + config.contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + config.contextTypes[contextKeys[i]] = React.PropTypes.object; + } + } + + return config; } function _markChildValidated(child) { From 2408a099b87e8172afd88934904523a8fbc49aa5 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 27 Oct 2017 08:57:04 -0700 Subject: [PATCH 05/31] Generate JS files --- lib/react.js | 59 +++++++++++++++++--------------- lib/react_prod.js | 59 +++++++++++++++++--------------- lib/react_with_addons.js | 59 +++++++++++++++++--------------- lib/react_with_react_dom_prod.js | 59 +++++++++++++++++--------------- 4 files changed, 124 insertions(+), 112 deletions(-) diff --git a/lib/react.js b/lib/react.js index 7de53d01..37fa6bee 100644 --- a/lib/react.js +++ b/lib/react.js @@ -4324,35 +4324,10 @@ module.exports = ReactPropTypesSecret; function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } -function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { - var childContextTypes; - var contextTypes; - - var childContextKeys = jsConfig && jsConfig.childContextKeys; - var contextKeys = jsConfig && jsConfig.contextKeys; - - if (childContextKeys && childContextKeys.length !== 0) { - childContextTypes = {}; - for (var i = 0; i < childContextKeys.length; i++) { - childContextTypes[childContextKeys[i]] = React.PropTypes.any; - } - } - - if (contextKeys && contextKeys.length !== 0) { - contextTypes = {}; - for (var i = 0; i < contextKeys.length; i++) { - contextTypes[contextKeys[i]] = React.PropTypes.any; - } - } - - return { - getChildContext: function() { - return dartInteropStatics.getChildContext(this.props.internal); - }, - childContextTypes: childContextTypes, - contextTypes: contextTypes, +function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics, jsConfig) { + var config = { getInitialState: function() { - this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); + this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, this.context, componentStatics); return {}; }, componentWillMount: function() { @@ -4380,6 +4355,34 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati return dartInteropStatics.handleRender(this.dartComponent); } }; + + // React limits the accessible context entries + // to the keys specified in childContextTypes/contextTypes. + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + config.childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; + } + + // Only declare this when `hasChildContext` is true to avoid unnecessarily + // creating interop context objects for components that won't use it. + config.getChildContext = function() { + return dartInteropStatics.getChildContext(this.dartComponent); + }; + } + + if (contextKeys && contextKeys.length !== 0) { + config.contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + config.contextTypes[contextKeys[i]] = React.PropTypes.object; + } + } + + return config; } function _markChildValidated(child) { diff --git a/lib/react_prod.js b/lib/react_prod.js index a831a5e7..48bdc81c 100644 --- a/lib/react_prod.js +++ b/lib/react_prod.js @@ -16,35 +16,10 @@ function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } -function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { - var childContextTypes; - var contextTypes; - - var childContextKeys = jsConfig && jsConfig.childContextKeys; - var contextKeys = jsConfig && jsConfig.contextKeys; - - if (childContextKeys && childContextKeys.length !== 0) { - childContextTypes = {}; - for (var i = 0; i < childContextKeys.length; i++) { - childContextTypes[childContextKeys[i]] = React.PropTypes.any; - } - } - - if (contextKeys && contextKeys.length !== 0) { - contextTypes = {}; - for (var i = 0; i < contextKeys.length; i++) { - contextTypes[contextKeys[i]] = React.PropTypes.any; - } - } - - return { - getChildContext: function() { - return dartInteropStatics.getChildContext(this.props.internal); - }, - childContextTypes: childContextTypes, - contextTypes: contextTypes, +function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics, jsConfig) { + var config = { getInitialState: function() { - this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); + this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, this.context, componentStatics); return {}; }, componentWillMount: function() { @@ -72,6 +47,34 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati return dartInteropStatics.handleRender(this.dartComponent); } }; + + // React limits the accessible context entries + // to the keys specified in childContextTypes/contextTypes. + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + config.childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; + } + + // Only declare this when `hasChildContext` is true to avoid unnecessarily + // creating interop context objects for components that won't use it. + config.getChildContext = function() { + return dartInteropStatics.getChildContext(this.dartComponent); + }; + } + + if (contextKeys && contextKeys.length !== 0) { + config.contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + config.contextTypes[contextKeys[i]] = React.PropTypes.object; + } + } + + return config; } function _markChildValidated(child) { diff --git a/lib/react_with_addons.js b/lib/react_with_addons.js index 4e821374..d00ac10e 100644 --- a/lib/react_with_addons.js +++ b/lib/react_with_addons.js @@ -5965,35 +5965,10 @@ module.exports = ReactPropTypesSecret; function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } -function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { - var childContextTypes; - var contextTypes; - - var childContextKeys = jsConfig && jsConfig.childContextKeys; - var contextKeys = jsConfig && jsConfig.contextKeys; - - if (childContextKeys && childContextKeys.length !== 0) { - childContextTypes = {}; - for (var i = 0; i < childContextKeys.length; i++) { - childContextTypes[childContextKeys[i]] = React.PropTypes.any; - } - } - - if (contextKeys && contextKeys.length !== 0) { - contextTypes = {}; - for (var i = 0; i < contextKeys.length; i++) { - contextTypes[contextKeys[i]] = React.PropTypes.any; - } - } - - return { - getChildContext: function() { - return dartInteropStatics.getChildContext(this.props.internal); - }, - childContextTypes: childContextTypes, - contextTypes: contextTypes, +function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics, jsConfig) { + var config = { getInitialState: function() { - this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); + this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, this.context, componentStatics); return {}; }, componentWillMount: function() { @@ -6021,6 +5996,34 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati return dartInteropStatics.handleRender(this.dartComponent); } }; + + // React limits the accessible context entries + // to the keys specified in childContextTypes/contextTypes. + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + config.childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; + } + + // Only declare this when `hasChildContext` is true to avoid unnecessarily + // creating interop context objects for components that won't use it. + config.getChildContext = function() { + return dartInteropStatics.getChildContext(this.dartComponent); + }; + } + + if (contextKeys && contextKeys.length !== 0) { + config.contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + config.contextTypes[contextKeys[i]] = React.PropTypes.object; + } + } + + return config; } function _markChildValidated(child) { diff --git a/lib/react_with_react_dom_prod.js b/lib/react_with_react_dom_prod.js index 595bb0e7..5083821f 100644 --- a/lib/react_with_react_dom_prod.js +++ b/lib/react_with_react_dom_prod.js @@ -16,35 +16,10 @@ function _getProperty(obj, key) { return obj[key]; } function _setProperty(obj, key, value) { return obj[key] = value; } -function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) { - var childContextTypes; - var contextTypes; - - var childContextKeys = jsConfig && jsConfig.childContextKeys; - var contextKeys = jsConfig && jsConfig.contextKeys; - - if (childContextKeys && childContextKeys.length !== 0) { - childContextTypes = {}; - for (var i = 0; i < childContextKeys.length; i++) { - childContextTypes[childContextKeys[i]] = React.PropTypes.any; - } - } - - if (contextKeys && contextKeys.length !== 0) { - contextTypes = {}; - for (var i = 0; i < contextKeys.length; i++) { - contextTypes[contextKeys[i]] = React.PropTypes.any; - } - } - - return { - getChildContext: function() { - return dartInteropStatics.getChildContext(this.props.internal); - }, - childContextTypes: childContextTypes, - contextTypes: contextTypes, +function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics, jsConfig) { + var config = { getInitialState: function() { - this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, componentStatics); + this.dartComponent = dartInteropStatics.initComponent(this, this.props.internal, this.context, componentStatics); return {}; }, componentWillMount: function() { @@ -72,6 +47,34 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati return dartInteropStatics.handleRender(this.dartComponent); } }; + + // React limits the accessible context entries + // to the keys specified in childContextTypes/contextTypes. + + var childContextKeys = jsConfig && jsConfig.childContextKeys; + var contextKeys = jsConfig && jsConfig.contextKeys; + + if (childContextKeys && childContextKeys.length !== 0) { + config.childContextTypes = {}; + for (var i = 0; i < childContextKeys.length; i++) { + config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; + } + + // Only declare this when `hasChildContext` is true to avoid unnecessarily + // creating interop context objects for components that won't use it. + config.getChildContext = function() { + return dartInteropStatics.getChildContext(this.dartComponent); + }; + } + + if (contextKeys && contextKeys.length !== 0) { + config.contextTypes = {}; + for (var i = 0; i < contextKeys.length; i++) { + config.contextTypes[contextKeys[i]] = React.PropTypes.object; + } + } + + return config; } function _markChildValidated(child) { From f77241952588906be639d6111e8fd129494a56c2 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 27 Oct 2017 09:16:51 -0700 Subject: [PATCH 06/31] Cleanup, naming, docs --- example/test/react_test_components.dart | 2 +- lib/react.dart | 14 +++++++++++- lib/react_client.dart | 20 ++++++++--------- lib/react_client/react_interop.dart | 30 ++++++++++++++++--------- 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/example/test/react_test_components.dart b/example/test/react_test_components.dart index c377bf4c..2e7d6021 100644 --- a/example/test/react_test_components.dart +++ b/example/test/react_test_components.dart @@ -157,7 +157,7 @@ class _ContextComponent extends react.Component { int _renderCount = 0; @override - Map getChildContext() => { + Map getChildContext() => { 'foo': 'bar', 'renderCount': _renderCount }; diff --git a/lib/react.dart b/lib/react.dart index 6f861537..5ceef3fb 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -39,6 +39,9 @@ abstract class Component { /// TODO: Switch back to a plain field once this issue is fixed. Ref _ref; + /// The React context map of this component, passed down from its ancestors' [getChildContext] value. + /// + /// Only keys declared in this component's [contextKeys] will be present. Map get context => _context; set context(Map value) => _context = value; @@ -253,10 +256,19 @@ abstract class Component { /// See: void componentWillUnmount() {} - Map getChildContext() => {}; + /// Returns a Map of context to be passed to descendant components. + /// + /// Only keys present in [childContextKeys] will be used; all others will be ignored. + Map getChildContext() => const {}; + /// The keys this component uses in its child context map (returned by [getChildContext]). + /// + /// __This method is called only once, upon component registration.__ Iterable get childContextKeys => const []; + /// The keys of context used by this component. + /// + /// __This method is called only once, upon component registration.__ Iterable get contextKeys => const []; /// Invoked once before the `Component` is mounted. The return value will be used as the initial value of [state]. diff --git a/lib/react_client.dart b/lib/react_client.dart index b4048278..567ae8f0 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -181,8 +181,8 @@ dynamic _convertArgsToChildren(List childrenArgs) { @JS('Object.keys') external List _objectKeys(Object object); -InteropContext _jsifyContext(Map context) { - var interopContext = new InteropContext(); +InteropContextValue _jsifyContext(Map context) { + var interopContext = new InteropContextValue(); context.forEach((key, value) { setProperty(interopContext, key, new ReactDartContextInternal(value)); }); @@ -190,7 +190,7 @@ InteropContext _jsifyContext(Map context) { return interopContext; } -Map _unjsifyContext(InteropContext interopContext) { +Map _unjsifyContext(InteropContextValue interopContext) { // TODO consider using `contextKeys` for this if perf of objectKeys is bad. return new Map.fromIterable(_objectKeys(interopContext), value: (key) { ReactDartContextInternal internal = getProperty(interopContext, key); @@ -203,7 +203,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { var zone = Zone.current; /// Wrapper for [Component.getInitialState]. - Component initComponent(ReactComponent jsThis, ReactDartComponentInternal internal, InteropContext context, ComponentStatics componentStatics) => zone.run(() { + Component initComponent(ReactComponent jsThis, ReactDartComponentInternal internal, InteropContextValue context, ComponentStatics componentStatics) => zone.run(() { void jsRedraw() { jsThis.setState(emptyJsMap); } @@ -225,7 +225,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { return component; }); - InteropContext getChildContext(Component component) => zone.run(() { + InteropContextValue getChildContext(Component component) => zone.run(() { return _jsifyContext(component.getChildContext()); }); @@ -250,7 +250,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// in `componentWillReceiveProps`. /// 2. Update [Component.state] by calling [Component.transferComponentState] /// 3. Update [Component.context] with the latest - void _afterPropsChange(Component component, InteropContext nextContext) { + void _afterPropsChange(Component component, InteropContextValue nextContext) { component.props = component.nextProps; // [1] component.transferComponentState(); // [2] // [3] @@ -275,7 +275,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { } /// Wrapper for [Component.componentWillReceiveProps]. - void handleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, InteropContext nextContext) => zone.run(() { + void handleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, InteropContextValue nextContext) => zone.run(() { var nextProps = _getNextProps(component, nextInternal); component ..nextProps = nextProps @@ -283,7 +283,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }); /// Wrapper for [Component.shouldComponentUpdate]. - bool handleShouldComponentUpdate(Component component, InteropContext nextContext) => zone.run(() { + bool handleShouldComponentUpdate(Component component, InteropContextValue nextContext) => zone.run(() { _callSetStateTransactionalCallbacks(component); if (component.shouldComponentUpdate(component.nextProps, component.nextState)) { @@ -299,7 +299,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { }); /// Wrapper for [Component.componentWillUpdate]. - void handleComponentWillUpdate(Component component, InteropContext nextContext) => zone.run(() { + void handleComponentWillUpdate(Component component, InteropContextValue nextContext) => zone.run(() { component.componentWillUpdate(component.nextProps, component.nextState); _afterPropsChange(component, nextContext); }); @@ -307,7 +307,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// Wrapper for [Component.componentDidUpdate]. /// /// Uses [prevState] which was transferred from [Component.nextState] in [componentWillUpdate]. - void handleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, InteropContext prevContext) => zone.run(() { + void handleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, InteropContextValue prevContext) => zone.run(() { var prevInternalProps = prevInternal.props; component.componentDidUpdate(prevInternalProps, component.prevState); _callSetStateCallbacks(component); diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index 71ad989f..092c86fe 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -148,7 +148,6 @@ class ReactElement { @anonymous class ReactComponent { external Component get dartComponent; - external InteropContext get context; external InteropProps get props; external get refs; external void setState(state, [callback]); @@ -161,10 +160,16 @@ class ReactComponent { // Interop internals // ---------------------------------------------------------------------------- +/// A JavaScript interop class representing a value in a React JS `context` object. +/// +/// Used for storing/accessing Dart [ReactDartContextInternal] objects in `context` +/// in a way that's opaque to the JS, and avoids the need to use dart2js interceptors. +/// +/// __For internal/advanced use only.__ @JS() @anonymous -class InteropContext { - external factory InteropContext(); +class InteropContextValue { + external factory InteropContextValue(); } /// A JavaScript interop class representing a React JS `props` object. @@ -187,8 +192,7 @@ class InteropProps { external factory InteropProps({ReactDartComponentInternal internal, String key, dynamic ref}); } -/// Internal react-dart information used to proxy React JS lifecycle to Dart -/// [Component] instances. +/// A Dart object that stores . /// /// __For internal/advanced use only.__ class ReactDartComponentInternal { @@ -199,6 +203,10 @@ class ReactDartComponentInternal { Map props; } +/// Internal react-dart information used to proxy React JS lifecycle to Dart +/// [Component] instances. +/// +/// __For internal/advanced use only.__ class ReactDartContextInternal { final dynamic value; @@ -234,14 +242,14 @@ external ReactClassConfig createReactDartComponentClassConfig( [JsComponentConfig jsConfig] ); -typedef Component _InitComponent(ReactComponent jsThis, ReactDartComponentInternal internal, InteropContext context, ComponentStatics componentStatics); -typedef InteropContext _GetChildContext(Component component); +typedef Component _InitComponent(ReactComponent jsThis, ReactDartComponentInternal internal, InteropContextValue context, ComponentStatics componentStatics); +typedef InteropContextValue _GetChildContext(Component component); typedef void _HandleComponentWillMount(Component component); typedef void _HandleComponentDidMount(Component component); -typedef void _HandleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, InteropContext nextContext); -typedef bool _HandleShouldComponentUpdate(Component component, InteropContext nextContext); -typedef void _HandleComponentWillUpdate(Component component, InteropContext nextContext); -typedef void _HandleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, InteropContext prevContext); +typedef void _HandleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, InteropContextValue nextContext); +typedef bool _HandleShouldComponentUpdate(Component component, InteropContextValue nextContext); +typedef void _HandleComponentWillUpdate(Component component, InteropContextValue nextContext); +typedef void _HandleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, InteropContextValue prevContext); typedef void _HandleComponentWillUnmount(Component component); typedef dynamic _HandleRender(Component component); From bb04f637ff85439d42c33976f2c8b4555b54e14d Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 27 Oct 2017 09:19:38 -0700 Subject: [PATCH 07/31] Ignore prevContext since it isn't supported in React 16 --- js_src/dart_helpers.js | 4 ++-- lib/react_client.dart | 2 +- lib/react_client/react_interop.dart | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/js_src/dart_helpers.js b/js_src/dart_helpers.js index 14fdb3b9..e65669b9 100644 --- a/js_src/dart_helpers.js +++ b/js_src/dart_helpers.js @@ -26,8 +26,8 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentWillUpdate: function(nextProps, nextState, nextContext) { dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState, prevContext) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); + componentDidUpdate: function(prevProps, prevState) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); diff --git a/lib/react_client.dart b/lib/react_client.dart index 567ae8f0..5f2c0f8f 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -307,7 +307,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// Wrapper for [Component.componentDidUpdate]. /// /// Uses [prevState] which was transferred from [Component.nextState] in [componentWillUpdate]. - void handleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, InteropContextValue prevContext) => zone.run(() { + void handleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal) => zone.run(() { var prevInternalProps = prevInternal.props; component.componentDidUpdate(prevInternalProps, component.prevState); _callSetStateCallbacks(component); diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index 092c86fe..0baab30d 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -249,7 +249,8 @@ typedef void _HandleComponentDidMount(Component component); typedef void _HandleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, InteropContextValue nextContext); typedef bool _HandleShouldComponentUpdate(Component component, InteropContextValue nextContext); typedef void _HandleComponentWillUpdate(Component component, InteropContextValue nextContext); -typedef void _HandleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal, InteropContextValue prevContext); +// Ignore prevContext in componentDidUpdate, since it's not supported in React 16 +typedef void _HandleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal); typedef void _HandleComponentWillUnmount(Component component); typedef dynamic _HandleRender(Component component); From b80ac0a48e99c24b4c1bc36d6e53c63d8333e137 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 27 Oct 2017 09:20:37 -0700 Subject: [PATCH 08/31] Generate JS --- lib/react.js | 4 ++-- lib/react_prod.js | 4 ++-- lib/react_with_addons.js | 4 ++-- lib/react_with_react_dom_prod.js | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/react.js b/lib/react.js index 37fa6bee..1651a0da 100644 --- a/lib/react.js +++ b/lib/react.js @@ -4345,8 +4345,8 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentWillUpdate: function(nextProps, nextState, nextContext) { dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState, prevContext) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); + componentDidUpdate: function(prevProps, prevState) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); diff --git a/lib/react_prod.js b/lib/react_prod.js index 48bdc81c..f425a8c5 100644 --- a/lib/react_prod.js +++ b/lib/react_prod.js @@ -37,8 +37,8 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentWillUpdate: function(nextProps, nextState, nextContext) { dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState, prevContext) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); + componentDidUpdate: function(prevProps, prevState) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); diff --git a/lib/react_with_addons.js b/lib/react_with_addons.js index d00ac10e..332d9db7 100644 --- a/lib/react_with_addons.js +++ b/lib/react_with_addons.js @@ -5986,8 +5986,8 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentWillUpdate: function(nextProps, nextState, nextContext) { dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState, prevContext) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); + componentDidUpdate: function(prevProps, prevState) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); diff --git a/lib/react_with_react_dom_prod.js b/lib/react_with_react_dom_prod.js index 5083821f..c421d153 100644 --- a/lib/react_with_react_dom_prod.js +++ b/lib/react_with_react_dom_prod.js @@ -37,8 +37,8 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati componentWillUpdate: function(nextProps, nextState, nextContext) { dartInteropStatics.handleComponentWillUpdate(this.dartComponent, nextContext); }, - componentDidUpdate: function(prevProps, prevState, prevContext) { - dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal, prevContext); + componentDidUpdate: function(prevProps, prevState) { + dartInteropStatics.handleComponentDidUpdate(this.dartComponent, prevProps.internal); }, componentWillUnmount: function() { dartInteropStatics.handleComponentWillUnmount(this.dartComponent); From bb0bc6cbd60a79f41281e8e99227ea3914cc0464 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 27 Oct 2017 10:12:50 -0700 Subject: [PATCH 09/31] Rename to handleGetChildContext --- js_src/dart_helpers.js | 2 +- lib/react.js | 2 +- lib/react_client.dart | 4 ++-- lib/react_client/react_interop.dart | 4 ++-- lib/react_prod.js | 2 +- lib/react_with_addons.js | 2 +- lib/react_with_react_dom_prod.js | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/js_src/dart_helpers.js b/js_src/dart_helpers.js index e65669b9..2819cef8 100644 --- a/js_src/dart_helpers.js +++ b/js_src/dart_helpers.js @@ -52,7 +52,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati // Only declare this when `hasChildContext` is true to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { - return dartInteropStatics.getChildContext(this.dartComponent); + return dartInteropStatics.handleGetChildContext(this.dartComponent); }; } diff --git a/lib/react.js b/lib/react.js index 1651a0da..3f26bc1a 100644 --- a/lib/react.js +++ b/lib/react.js @@ -4371,7 +4371,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati // Only declare this when `hasChildContext` is true to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { - return dartInteropStatics.getChildContext(this.dartComponent); + return dartInteropStatics.handleGetChildContext(this.dartComponent); }; } diff --git a/lib/react_client.dart b/lib/react_client.dart index 5f2c0f8f..ebe170db 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -225,7 +225,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { return component; }); - InteropContextValue getChildContext(Component component) => zone.run(() { + InteropContextValue handleGetChildContext(Component component) => zone.run(() { return _jsifyContext(component.getChildContext()); }); @@ -331,7 +331,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { return new ReactDartInteropStatics( initComponent: allowInterop(initComponent), - getChildContext: allowInterop(getChildContext), + handleGetChildContext: allowInterop(handleGetChildContext), handleComponentWillMount: allowInterop(handleComponentWillMount), handleComponentDidMount: allowInterop(handleComponentDidMount), handleComponentWillReceiveProps: allowInterop(handleComponentWillReceiveProps), diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index 0baab30d..10296df5 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -243,7 +243,7 @@ external ReactClassConfig createReactDartComponentClassConfig( ); typedef Component _InitComponent(ReactComponent jsThis, ReactDartComponentInternal internal, InteropContextValue context, ComponentStatics componentStatics); -typedef InteropContextValue _GetChildContext(Component component); +typedef InteropContextValue _HandleGetChildContext(Component component); typedef void _HandleComponentWillMount(Component component); typedef void _HandleComponentDidMount(Component component); typedef void _HandleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, InteropContextValue nextContext); @@ -260,7 +260,7 @@ typedef dynamic _HandleRender(Component component); class ReactDartInteropStatics { external factory ReactDartInteropStatics({ _InitComponent initComponent, - _GetChildContext getChildContext, + _HandleGetChildContext handleGetChildContext, _HandleComponentWillMount handleComponentWillMount, _HandleComponentDidMount handleComponentDidMount, _HandleComponentWillReceiveProps handleComponentWillReceiveProps, diff --git a/lib/react_prod.js b/lib/react_prod.js index f425a8c5..718358c7 100644 --- a/lib/react_prod.js +++ b/lib/react_prod.js @@ -63,7 +63,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati // Only declare this when `hasChildContext` is true to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { - return dartInteropStatics.getChildContext(this.dartComponent); + return dartInteropStatics.handleGetChildContext(this.dartComponent); }; } diff --git a/lib/react_with_addons.js b/lib/react_with_addons.js index 332d9db7..46e5cc6f 100644 --- a/lib/react_with_addons.js +++ b/lib/react_with_addons.js @@ -6012,7 +6012,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati // Only declare this when `hasChildContext` is true to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { - return dartInteropStatics.getChildContext(this.dartComponent); + return dartInteropStatics.handleGetChildContext(this.dartComponent); }; } diff --git a/lib/react_with_react_dom_prod.js b/lib/react_with_react_dom_prod.js index c421d153..b2fbb6bc 100644 --- a/lib/react_with_react_dom_prod.js +++ b/lib/react_with_react_dom_prod.js @@ -63,7 +63,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati // Only declare this when `hasChildContext` is true to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { - return dartInteropStatics.getChildContext(this.dartComponent); + return dartInteropStatics.handleGetChildContext(this.dartComponent); }; } From 9989c2ea827232c056f0e89134ea3983acd583f5 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 27 Oct 2017 15:57:07 -0500 Subject: [PATCH 10/31] Add prev/nextContext and update docs --- lib/react.dart | 107 ++++++++++++++++++++++++++++++++++++++++-- lib/react_client.dart | 36 +++++++++++--- 2 files changed, 133 insertions(+), 10 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index 5ceef3fb..58b0ce92 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -97,8 +97,12 @@ abstract class Component { _initProps(props); } + /// Initializes context _initContext(context) { - _context = new Map.from(context ?? {}); + this.context = new Map.from(context ?? {}); + + /// Call `transferComponentContext` to get state also to `_prevContext` + transferComponentContext(); } _initProps(props) { @@ -108,15 +112,30 @@ abstract class Component { initStateInternal() { this.state = new Map.from(getInitialState()); - // Call `transferComponentState` to get state also to `_prevState` + + /// Call `transferComponentState` to get state also to `_prevState` transferComponentState(); } + /// Private reference to the value of [context] for the upcoming render cycle. + /// + /// Useful for ReactJS lifecycle methods [shouldComponentUpdateWithContext], [componentWillUpdateWithContext] and + /// [componentDidUpdateWithContext]. + Map _nextContext = null; + /// Private reference to the value of [state] for the upcoming render cycle. /// /// Useful for ReactJS lifecycle methods [shouldComponentUpdate], [componentWillUpdate] and [componentDidUpdate]. Map _nextState = null; + /// Reference to the value of [context] from the previous render cycle, used internally for proxying + /// the ReactJS lifecycle method and [componentDidUpdateWithContext]. + /// + /// Not available after [componentDidUpdateWithContext] is called. + /// + /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. + Map prevContext; + /// Reference to the value of [state] from the previous render cycle, used internally for proxying /// the ReactJS lifecycle method and [componentDidUpdate]. /// @@ -125,6 +144,11 @@ abstract class Component { /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. Map prevState; + /// Public getter for [_nextState]. + /// + /// If `null`, then [_nextContext] is equal to [context] - which is the value that will be returned. + Map get nextContext => _nextContext == null ? context : _nextContext; + /// Public getter for [_nextState]. /// /// If `null`, then [_nextState] is equal to [state] - which is the value that will be returned. @@ -132,11 +156,21 @@ abstract class Component { /// Reference to the value of [props] for the upcoming render cycle. /// - /// Used internally for proxying ReactJS lifecycle methods [shouldComponentUpdate], [componentWillReceiveProps], and [componentWillUpdate]. + /// Used internally for proxying ReactJS lifecycle methods [shouldComponentUpdate], [componentWillReceiveProps], and + /// [componentWillUpdate] as well as the context-specific variants. /// /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. Map nextProps; + /// Transfers `Component` [_nextContext] to [context], and [context] to [prevContext]. + void transferComponentContext() { + prevContext = context; + if (_nextContext != null) { + context = _nextContext; + } + _nextContext = new Map.from(context); + } + /// Transfers `Component` [_nextState] to [state], and [state] to [prevState]. void transferComponentState() { prevState = state; @@ -217,17 +251,50 @@ abstract class Component { /// /// Calling [setState] within this function will not trigger an additional [render]. /// + /// __Note__: Choose either this method or [componentWillReceivePropsWithContext]. They are both called at the same + /// time so using both provides no added benefit. + /// /// See: void componentWillReceiveProps(Map newProps) {} + /// ReactJS lifecycle method that is invoked when a `Component` is receiving [newProps]. + /// + /// This method is not called for the initial [render]. + /// + /// Use this as an opportunity to react to a prop or context transition before [render] is called by updating the + /// [state] using [setState]. The old props and context can be accessed via [props] and [context], respectively. + /// + /// Calling [setState] within this function will not trigger an additional [render]. + /// + /// __Note__: Choose either this method or [componentWillReceiveProps]. They are both called at the same time so using + /// both provides no added benefit. + /// + /// See: + void componentWillReceivePropsWithContext(Map newProps, nextContext) {} + /// 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 + /// 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. /// + /// __Note__: This method is called after [shouldComponentUpdateWithContext]. When it returns `null`, the result of + /// this method is used, but this is not called if a valid `bool` is returned from [shouldComponentUpdateWithContext]. + /// /// See: bool shouldComponentUpdate(Map nextProps, Map nextState) => true; + /// ReactJS lifecycle method that is invoked before rendering when [nextProps], [nextState], or [nextContext] are + /// being received. + /// + /// Use this as an opportunity to return `false` when you're certain that the transition to the new props, state, and + /// context will not require a component update. + /// + /// __Note__: This method is called before [shouldComponentUpdate]. Returning `null` will defer the update to the + /// result of [shouldComponentUpdate], but [shouldComponentUpdate] is not called if a valid `bool` is returned. + /// + /// See: + bool shouldComponentUpdateWithContext(Map nextProps, Map nextState, Map nextContext) => null; + /// ReactJS lifecycle method that is invoked immediately before rendering when [nextProps] or [nextState] are being /// received. /// @@ -235,9 +302,25 @@ abstract class Component { /// /// Use this as an opportunity to perform preparation before an update occurs. /// + /// __Note__: Choose either this method or [componentWillUpdateWithContext]. They are both called at the same time so + /// using both provides no added benefit. + /// /// See: void componentWillUpdate(Map nextProps, Map nextState) {} + /// ReactJS lifecycle method that is invoked immediately before rendering when [nextProps], [nextState], or + /// [nextContext] are being received. + /// + /// This method is not called for the initial [render]. + /// + /// Use this as an opportunity to perform preparation before an update occurs. + /// + /// __Note__: Choose either this method or [componentWillUpdate]. They are both called at the same time so using both + /// provides no added benefit. + /// + /// See: + void componentWillUpdateWithContext(Map nextProps, Map nextState, Map nextContext) {} + /// ReactJS lifecycle method that is invoked immediately after the `Component`'s updates are flushed to the DOM. /// /// This method is not called for the initial [render]. @@ -245,9 +328,25 @@ 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]. /// + /// __Note__: Choose either this method or [componentDidUpdateWithContext]. They are both called at the same time so + /// using both provides no added benefit. + /// /// See: void componentDidUpdate(Map prevProps, Map prevState) {} + /// ReactJS lifecycle method that is invoked immediately after the `Component`'s updates are flushed to the DOM. + /// + /// This method is not called for the initial [render]. + /// + /// 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] /[prevContext]. + /// + /// __Note__: Choose either this method or [componentDidUpdate]. They are both called at the same time so using both + /// provides no added benefit. + /// + /// See: + void componentDidUpdateWithContext(Map prevProps, Map prevState, Map prevContext) {} + /// 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 diff --git a/lib/react_client.dart b/lib/react_client.dart index ebe170db..22cd4e50 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -233,7 +233,8 @@ final ReactDartInteropStatics _dartInteropStatics = (() { void handleComponentWillMount(Component component) => zone.run(() { component ..componentWillMount() - ..transferComponentState(); + ..transferComponentState() + ..transferComponentContext(); }); /// Wrapper for [Component.componentDidMount]. @@ -249,12 +250,15 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// 1. Update [Component.props] using the value stored to [Component.nextProps] /// in `componentWillReceiveProps`. /// 2. Update [Component.state] by calling [Component.transferComponentState] - /// 3. Update [Component.context] with the latest + /// 3. Update [Component.context] by calling [Component.transferComponentContext] void _afterPropsChange(Component component, InteropContextValue nextContext) { component.props = component.nextProps; // [1] component.transferComponentState(); // [2] - // [3] - component.context = _unjsifyContext(nextContext); + component.transferComponentContext(); // [3] + } + + void _clearPrevContext(Component component) { + component.prevContext = null; } void _clearPrevState(Component component) { @@ -286,7 +290,16 @@ final ReactDartInteropStatics _dartInteropStatics = (() { bool handleShouldComponentUpdate(Component component, InteropContextValue nextContext) => zone.run(() { _callSetStateTransactionalCallbacks(component); - if (component.shouldComponentUpdate(component.nextProps, component.nextState)) { + // If shouldComponentUpdateWithContext returns a valid bool (default implementation returns null), + // then don't bother calling `shouldComponentUpdate` and have it trump. + bool shouldUpdate = + component.shouldComponentUpdateWithContext(component.nextProps, component.nextState, component.nextContext); + + if (shouldUpdate == null) { + shouldUpdate = component.shouldComponentUpdate(component.nextProps, component.nextState); + } + + if (shouldUpdate) { return true; } else { // If component should not update, update props / transfer state because componentWillUpdate will not be called. @@ -294,13 +307,18 @@ final ReactDartInteropStatics _dartInteropStatics = (() { _callSetStateCallbacks(component); // Clear out prevState after it's done being used so it's not retained _clearPrevState(component); + // Clear out prevContext after it's done being used so it's not retained + _clearPrevContext(component); return false; - } + } }); /// Wrapper for [Component.componentWillUpdate]. void handleComponentWillUpdate(Component component, InteropContextValue nextContext) => zone.run(() { + /// Call `componentWillUpdate` and the context variant component.componentWillUpdate(component.nextProps, component.nextState); + component.componentWillUpdateWithContext(component.nextProps, component.nextState, component.nextContext); + _afterPropsChange(component, nextContext); }); @@ -309,10 +327,16 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// Uses [prevState] which was transferred from [Component.nextState] in [componentWillUpdate]. void handleComponentDidUpdate(Component component, ReactDartComponentInternal prevInternal) => zone.run(() { var prevInternalProps = prevInternal.props; + + /// Call `componentDidUpdate` and the context variant component.componentDidUpdate(prevInternalProps, component.prevState); + component.componentDidUpdateWithContext(prevInternalProps, component.prevState, component.prevContext); + _callSetStateCallbacks(component); // Clear out prevState after it's done being used so it's not retained _clearPrevState(component); + // Clear out prevContext after it's done being used so it's not retained + _clearPrevContext(component); }); /// Wrapper for [Component.componentWillUnmount]. From 2e7d74bb2c0f12ae951677a3e7e1decbc95a9418 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 27 Oct 2017 16:14:27 -0500 Subject: [PATCH 11/31] Added logging to js build --- tool/build_js.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tool/build_js.sh b/tool/build_js.sh index b40eb643..0395a0fe 100755 --- a/tool/build_js.sh +++ b/tool/build_js.sh @@ -12,12 +12,25 @@ DART_HELPERS_JS="js_src/dart_helpers.js" rm lib/*.js cat js_src/react.js $DART_HELPERS_JS > lib/react.js +echo 'Created lib/react.js' + cat js_src/react.min.js $DART_HELPERS_JS > lib/react_prod.js +echo 'Created lib/react_prod.js' + cat js_src/react-with-addons.js $DART_HELPERS_JS > lib/react_with_addons.js +echo 'Created lib/react_with_addons.js' cp js_src/react-dom.js lib/react_dom.js +echo 'Created lib/react_dom.js' + cp js_src/react-dom.min.js lib/react_dom_prod.js +echo 'Created lib/react_dom_prod.js' + cp js_src/react-dom-server.js lib/react_dom_server.js +echo 'Created lib/react_dom_server.js' + cp js_src/react-dom-server.min.js lib/react_dom_server_prod.js +echo 'Created lib/react_dom_server_prod.js' cat lib/react_prod.js lib/react_dom_prod.js > lib/react_with_react_dom_prod.js +echo 'Created lib/react_with_react_dom_prod.js' From f605e0d46673e6a71a1ad8da4157bf4b3bac37e8 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 27 Oct 2017 16:15:11 -0500 Subject: [PATCH 12/31] Added lifecycle call checks to LifecycleTest --- test/lifecycle_test.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/lifecycle_test.dart b/test/lifecycle_test.dart index 434cab09..f2bab151 100644 --- a/test/lifecycle_test.dart +++ b/test/lifecycle_test.dart @@ -546,16 +546,29 @@ class _LifecycleTest extends react.Component { void componentWillReceiveProps(newProps) => lifecycleCall('componentWillReceiveProps', arguments: [new Map.from(newProps)]); + void componentWillReceivePropsWithContext(newProps, newContext) => + lifecycleCall('componentWillReceivePropsWithContext', arguments: [new Map.from(newProps), new Map.from(newContext)]); + void componentWillUpdate(nextProps, nextState) => lifecycleCall('componentWillUpdate', arguments: [new Map.from(nextProps), new Map.from(nextState)]); + void componentWillUpdateWithContext(nextProps, nextState, nextContext) => + lifecycleCall('componentWillUpdateWithContext', arguments: [new Map.from(nextProps), new Map.from(nextState), new Map.from(nextContext)]); + void componentDidUpdate(prevProps, prevState) => lifecycleCall('componentDidUpdate', arguments: [new Map.from(prevProps), new Map.from(prevState)]); + void componentDidUpdateWithContext(prevProps, prevState, prevContext) => + lifecycleCall('componentDidUpdateWithContext', arguments: [new Map.from(prevProps), new Map.from(prevState), new Map.from(prevContext)]); + bool shouldComponentUpdate(nextProps, nextState) => lifecycleCall('shouldComponentUpdate', arguments: [new Map.from(nextProps), new Map.from(nextState)], defaultReturnValue: () => true); + bool shouldComponentUpdateWithContext(nextProps, nextState, nextContext) => + lifecycleCall('shouldComponentUpdateWithContext', arguments: [new Map.from(nextProps), new Map.from(nextState), new Map.from(nextContext)], + defaultReturnValue: () => true); + dynamic render() => lifecycleCall('render', defaultReturnValue: () => react.div({})); From 88bb52f852dfc5667631e0d707239f2a0ca67059 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 27 Oct 2017 18:02:47 -0500 Subject: [PATCH 13/31] Fix errors in react_test examples --- example/test/react_test.dart | 6 +++--- example/test/react_test_components.dart | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/example/test/react_test.dart b/example/test/react_test.dart index 3cf14128..2e572a9c 100644 --- a/example/test/react_test.dart +++ b/example/test/react_test.dart @@ -8,10 +8,10 @@ import "react_test_components.dart"; void main() { setClientConfiguration(); react_dom.render(mainComponent({}, [ - helloGreeter({}, []), - listComponent({}, []), + helloGreeter({'key': 'hello'}, []), + listComponent({'key': 'list'}, []), //clockComponent({"name": 'my-clock'}, []), - checkBoxComponent({}, []) + checkBoxComponent({'key': 'checkbox'}, []) ] ), querySelector('#content')); } diff --git a/example/test/react_test_components.dart b/example/test/react_test_components.dart index 2e7d6021..eb55a056 100644 --- a/example/test/react_test_components.dart +++ b/example/test/react_test_components.dart @@ -30,8 +30,8 @@ class _HelloGreeter extends react.Component { render() { return react.div({}, [ - react.input({'ref': 'myInput', 'value': bind('name'), 'onChange': onInputChange}), - helloComponent({'name': state['name']}) + react.input({'key': 'input', 'ref': 'myInput', 'value': bind('name'), 'onChange': onInputChange}), + helloComponent({'key': 'hello', 'name': state['name']}) ]); } } @@ -47,8 +47,8 @@ class _CheckBoxComponent extends react.Component { render() { return react.div({}, [ - react.label({'className': this.state["checked"] ? 'striked' : 'not-striked'}, 'do the dishes'), - react.input({'type': 'checkbox', 'value': bind('checked')}, []) + react.label({'key': 'label', 'className': this.state["checked"] ? 'striked' : 'not-striked'}, 'do the dishes'), + react.input({'key': 'input', 'type': 'checkbox', 'value': bind('checked')}) ]); } } @@ -136,8 +136,8 @@ class _ListComponent extends react.Component { } return react.div({}, [ - react.button({"onClick": addItem}, "addItem"), - react.ul({}, items), + react.button({"onClick": addItem, 'key': 'button'}, "addItem"), + react.ul({'key': 'list'}, items), ]); } } From b410e628b23b559e6bf6b2bfed7c75d9ac6ecf55 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 27 Oct 2017 18:09:15 -0500 Subject: [PATCH 14/31] Fix errors in ref_test examples --- example/test/ref_test.dart | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/example/test/ref_test.dart b/example/test/ref_test.dart index 770268cf..5e7303f1 100644 --- a/example/test/ref_test.dart +++ b/example/test/ref_test.dart @@ -45,23 +45,23 @@ class _ParentComponent extends react.Component { render() => react.div({},[ - react.h1({}, "String refs"), - react.h4({}, ""), - react.input({"ref": "inputRef"}), - react.button({"onClick": showInputValue}, "Print input element value"), - react.h4({}, "ChildComponent"), - ChildComponent({"ref": "childRef"}), - react.button({"onClick": showChildValue}, "Print child value"), - react.button({"onClick": incrementChildValue}, "Increment child value"), + react.h1({'key': 'string-h1'}, "String refs"), + react.h4({'key': 'string-h4'}, ""), + react.input({'key': 'string-input', "ref": "inputRef"}), + react.button({'key': 'string-show-input', "onClick": showInputValue}, "Print input element value"), + react.h4({'key': 'string-h4-child'}, "ChildComponent"), + ChildComponent({'key': 'string-child', "ref": "childRef"}), + react.button({'key': 'string-show-button', "onClick": showChildValue}, "Print child value"), + react.button({'key': 'string-increment-button', "onClick": incrementChildValue}, "Increment child value"), - react.h1({}, "Callback refs"), - react.h4({}, ""), - react.input({"ref": (instance) => _inputCallbackRef = instance}), - react.button({"onClick": showInputCallbackRefValue}, "Print input element value"), - react.h4({}, "ChildComponent"), - ChildComponent({"ref": (instance) => _childCallbackRef = instance}), - react.button({"onClick": showChildCallbackRefValue}, "Print child value"), - react.button({"onClick": incrementChildCallbackRefValue}, "Increment child value"), + react.h1({'key': 'h1-callback'}, "Callback refs"), + react.h4({'key': 'h4-callback-input'}, ""), + react.input({'key': 'callback-input', "ref": (instance) => _inputCallbackRef = instance}), + react.button({'key': 'callback-show-input', "onClick": showInputCallbackRefValue}, "Print input element value"), + react.h4({'key': 'callback-child-h4'}, "ChildComponent"), + ChildComponent({'key': 'callback-child', "ref": (instance) => _childCallbackRef = instance}), + react.button({'key': 'callback-show-button', "onClick": showChildCallbackRefValue}, "Print child value"), + react.button({'key': 'callback-increment-button', "onClick": incrementChildCallbackRefValue}, "Increment child value"), ]); } From a8585e8056bef319a651ca1225b1be3adeeba018 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 27 Oct 2017 18:11:21 -0500 Subject: [PATCH 15/31] Fix errors in speed_test examples --- example/test/speed_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/test/speed_test.dart b/example/test/speed_test.dart index af7288a2..e52310b2 100644 --- a/example/test/speed_test.dart +++ b/example/test/speed_test.dart @@ -56,9 +56,9 @@ class _Hello extends react.Component { for(var elem in data){ children.add( react.div({'key': elem[0]},[ - react.span({}, elem[0]), + react.span({'key': 'span1'}, elem[0]), " ", - react.span({}, elem[1]) + react.span({'key': 'span2'}, elem[1]) ]) ); } From 2fba623b9ab1bd19c9113e3118dda81b2219ab9f Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 27 Oct 2017 18:15:57 -0500 Subject: [PATCH 16/31] Fix errors in get_dom_node_test examples --- example/test/get_dom_node_test.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/example/test/get_dom_node_test.dart b/example/test/get_dom_node_test.dart index 76b55b37..da54ca77 100644 --- a/example/test/get_dom_node_test.dart +++ b/example/test/get_dom_node_test.dart @@ -20,7 +20,7 @@ class _ChildComponent extends react.Component { react.div({}, [ "Test element", counter.toString(), - react.button({"onClick": (_) { counter++;redraw();} }, "Increase counter") + react.button({'key': 'button', "onClick": (_) { counter++;redraw();} }, "Increase counter") ]); } @@ -40,12 +40,12 @@ class SimpleComponent extends react.Component { render() => react.div({}, [ - react.span({"ref": "refToSpan"}, "Test"), - react.span({}, counter), - react.button({"onClick": (_) => (react_dom.findDOMNode(this) as HtmlElement).children.first.text = (++counter).toString()},"Increase counter"), - react.br({}), - ChildComponent({"ref": "refToElement"}), - react.button({"onClick": (_) => window.alert((this.ref('refToElement') as _ChildComponent).counter.toString())}, "Show value of child element"), + react.span({'key': 'span1', "ref": "refToSpan"}, "Test"), + react.span({'key': 'span2'}, counter), + react.button({'key': 'button1', "onClick": (_) => (react_dom.findDOMNode(this) as HtmlElement).children.first.text = (++counter).toString()},"Increase counter"), + react.br({'key': 'br'}), + ChildComponent({'key': 'child', "ref": "refToElement"}), + react.button({'key': 'button2', "onClick": (_) => window.alert((this.ref('refToElement') as _ChildComponent).counter.toString())}, "Show value of child element"), ]); } From cd9ddafb9733b109ab385c7819c5c8a512a973f5 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 27 Oct 2017 18:26:49 -0500 Subject: [PATCH 17/31] Adjust context component to test childContextKeys --- example/test/context_test.dart | 4 + example/test/react_test_components.dart | 20 ++++- lib/react_client.dart | 13 +-- lib/react_server.dart | 3 + lib/react_test.dart | 2 +- test/ReactSetStateTestComponent.js | 17 ++++ test/lifecycle_test.dart | 106 ++++++++++++++++++------ 7 files changed, 132 insertions(+), 33 deletions(-) diff --git a/example/test/context_test.dart b/example/test/context_test.dart index 2b7673f1..bf94d844 100644 --- a/example/test/context_test.dart +++ b/example/test/context_test.dart @@ -15,4 +15,8 @@ void main() { react_dom.render(contextComponent({}, contextConsumerComponent({}), ), querySelector('#content')); + + react_dom.render(contextComponent({}, + contextConsumerComponent({}, fooOnlyContextConsumerComponent({})), + ), querySelector('#content')); } diff --git a/example/test/react_test_components.dart b/example/test/react_test_components.dart index eb55a056..92978da7 100644 --- a/example/test/react_test_components.dart +++ b/example/test/react_test_components.dart @@ -158,12 +158,13 @@ class _ContextComponent extends react.Component { @override Map getChildContext() => { - 'foo': 'bar', + 'foo': {'object': 'with value'}, + 'bar': true, 'renderCount': _renderCount }; @override - Iterable get childContextKeys => const ['foo', 'renderCount']; + Iterable get childContextKeys => const ['foo', 'bar', 'renderCount']; render() { _renderCount++; @@ -177,7 +178,6 @@ class _ContextComponent extends react.Component { } var contextComponent = react.registerComponent(() => new _ContextComponent()); - class _ContextConsumerComponent extends react.Component { @override Iterable get contextKeys => const ['foo', 'renderCount']; @@ -186,7 +186,21 @@ class _ContextConsumerComponent extends react.Component { return react.ul({}, 'ContextConsumerComponent.context: ', context.toString(), + props['children'] ); } } var contextConsumerComponent = react.registerComponent(() => new _ContextConsumerComponent()); + +class _FooOnlyContextConsumerComponent extends react.Component { + @override + Iterable get contextKeys => const ['foo']; + + render() { + return react.ul({}, + 'FooOnlyContextConsumerComponent.context: ', + context.toString(), + ); + } +} +var fooOnlyContextConsumerComponent = react.registerComponent(() => new _FooOnlyContextConsumerComponent()); diff --git a/lib/react_client.dart b/lib/react_client.dart index 22cd4e50..2ba95b9a 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -283,7 +283,8 @@ final ReactDartInteropStatics _dartInteropStatics = (() { var nextProps = _getNextProps(component, nextInternal); component ..nextProps = nextProps - ..componentWillReceiveProps(nextProps); + ..componentWillReceiveProps(nextProps) + ..componentWillReceivePropsWithContext(nextProps, component.nextContext); }); /// Wrapper for [Component.shouldComponentUpdate]. @@ -316,8 +317,9 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// Wrapper for [Component.componentWillUpdate]. void handleComponentWillUpdate(Component component, InteropContextValue nextContext) => zone.run(() { /// Call `componentWillUpdate` and the context variant - component.componentWillUpdate(component.nextProps, component.nextState); - component.componentWillUpdateWithContext(component.nextProps, component.nextState, component.nextContext); + component + ..componentWillUpdate(component.nextProps, component.nextState) + ..componentWillUpdateWithContext(component.nextProps, component.nextState, component.nextContext); _afterPropsChange(component, nextContext); }); @@ -329,8 +331,9 @@ final ReactDartInteropStatics _dartInteropStatics = (() { var prevInternalProps = prevInternal.props; /// Call `componentDidUpdate` and the context variant - component.componentDidUpdate(prevInternalProps, component.prevState); - component.componentDidUpdateWithContext(prevInternalProps, component.prevState, component.prevContext); + component + ..componentDidUpdate(prevInternalProps, component.prevState) + ..componentDidUpdateWithContext(prevInternalProps, component.prevState, component.prevContext); _callSetStateCallbacks(component); // Clear out prevState after it's done being used so it's not retained diff --git a/lib/react_server.dart b/lib/react_server.dart index 62074920..c0fe2f83 100644 --- a/lib/react_server.dart +++ b/lib/react_server.dart @@ -45,6 +45,9 @@ ReactComponentFactory _registerComponent(ComponentFactory componentFactory, [Ite // If componentWillMount called setState or replaceState, then transfer this state to actual state. component.transferComponentState(); + // If componentWillMount updated context, then transfer this context to actual context. + component.transferComponentContext(); + // Return result of component.render() return ([String ownerId, num position, String key]) { return component.render()(ownerId, position, key); diff --git a/lib/react_test.dart b/lib/react_test.dart index 86b21336..a2502b38 100644 --- a/lib/react_test.dart +++ b/lib/react_test.dart @@ -34,7 +34,7 @@ initializeComponent(Component component, [Map props = const {}, List children, r if (redraw == null) redraw = () {}; var extendedProps = new Map.from(component.getDefaultProps()) ..addAll(props); - component.initComponentInternal(extendedProps, redraw, ref); + component.initComponentInternal(extendedProps, redraw, ref, null, component.context); component.initStateInternal(); component.componentWillMount(); } diff --git a/test/ReactSetStateTestComponent.js b/test/ReactSetStateTestComponent.js index deec9c7c..668400bf 100644 --- a/test/ReactSetStateTestComponent.js +++ b/test/ReactSetStateTestComponent.js @@ -27,19 +27,36 @@ var ReactSetStateTestComponent = React.createClass({ this.recordLifecyleCall("componentWillReceiveProps"); }, + componentWillReceivePropsWithContext: function(_) { + this.recordLifecyleCall("componentWillReceivePropsWithContext"); + }, + shouldComponentUpdate: function(_, __) { this.recordLifecyleCall("shouldComponentUpdate"); return this.props.shouldUpdate; }, + shouldComponentUpdateWithContext: function(_, __, ___) { + this.recordLifecyleCall("shouldComponentUpdateWithContext"); + return this.props.shouldUpdate; + }, + componentWillUpdate: function(_, __) { this.recordLifecyleCall("componentWillUpdate"); }, + componentWillUpdateWithContext: function(_, __, ___) { + this.recordLifecyleCall("componentWillUpdateWithContext"); + }, + componentDidUpdate: function(_, __) { this.recordLifecyleCall("componentDidUpdate"); }, + componentDidUpdateWithContext: function(_, __, ___) { + this.recordLifecyleCall("componentDidUpdateWithContext"); + }, + outerSetStateCallback: function() { this.recordLifecyleCall('outerSetStateCallback'); }, diff --git a/test/lifecycle_test.dart b/test/lifecycle_test.dart index f2bab151..97556b60 100644 --- a/test/lifecycle_test.dart +++ b/test/lifecycle_test.dart @@ -159,6 +159,7 @@ void main() { ); const Map expectedState = const {}; + const Map expectedContext = const {}; var mountNode = new DivElement(); var instance = react_dom.render(LifecycleTest(initialProps), mountNode); @@ -169,11 +170,14 @@ void main() { react_dom.render(LifecycleTest(newProps), mountNode); expect(component.lifecycleCalls, equals([ - matchCall('componentWillReceiveProps', args: [newPropsWithDefaults], props: initialPropsWithDefaults), - matchCall('shouldComponentUpdate', args: [newPropsWithDefaults, expectedState], props: initialPropsWithDefaults), - matchCall('componentWillUpdate', args: [newPropsWithDefaults, expectedState], props: initialPropsWithDefaults), - matchCall('render', props: newPropsWithDefaults), - matchCall('componentDidUpdate', args: [initialPropsWithDefaults, expectedState], props: newPropsWithDefaults), + matchCall('componentWillReceiveProps', args: [newPropsWithDefaults], props: initialPropsWithDefaults), + matchCall('componentWillReceivePropsWithContext', args: [newPropsWithDefaults, expectedContext], props: initialPropsWithDefaults), + matchCall('shouldComponentUpdateWithContext', args: [newPropsWithDefaults, expectedState, expectedContext], props: initialPropsWithDefaults), + matchCall('componentWillUpdate', args: [newPropsWithDefaults, expectedState], props: initialPropsWithDefaults), + matchCall('componentWillUpdateWithContext', args: [newPropsWithDefaults, expectedState, expectedContext], props: initialPropsWithDefaults), + matchCall('render', props: newPropsWithDefaults), + matchCall('componentDidUpdate', args: [initialPropsWithDefaults, expectedState], props: newPropsWithDefaults), + matchCall('componentDidUpdateWithContext', args: [initialPropsWithDefaults, expectedState, expectedContext], props: newPropsWithDefaults), ])); }); @@ -193,6 +197,8 @@ void main() { 'getInitialState': (_) => initialState }); + final Map newContext = const {}; + final Map expectedProps = unmodifiableMap( defaultProps, initialProps, @@ -206,10 +212,12 @@ void main() { component.setState(stateDelta); expect(component.lifecycleCalls, equals([ - matchCall('shouldComponentUpdate', args: [expectedProps, newState], state: initialState), - matchCall('componentWillUpdate', args: [expectedProps, newState], state: initialState), - matchCall('render', state: newState), - matchCall('componentDidUpdate', args: [expectedProps, initialState], state: newState), + matchCall('shouldComponentUpdateWithContext', args: [expectedProps, newState, newContext], state: initialState), + matchCall('componentWillUpdate', args: [expectedProps, newState], state: initialState), + matchCall('componentWillUpdateWithContext', args: [expectedProps, newState, newContext], state: initialState), + matchCall('render', state: newState), + matchCall('componentDidUpdate', args: [expectedProps, initialState], state: newState), + matchCall('componentDidUpdateWithContext', args: [expectedProps, initialState, newContext], state: newState), ])); }); @@ -224,6 +232,7 @@ void main() { const Map stateDelta = const { 'newState': 'new', }; + const Map expectedContext = const {}; final Map lifecycleTestProps = unmodifiableMap({ 'getInitialState': (_) => initialState, @@ -255,17 +264,22 @@ void main() { expect(component.lifecycleCalls, equals([ matchCall('componentWillReceiveProps', args: [newPropsWithDefaults], props: initialPropsWithDefaults, state: initialState), - matchCall('shouldComponentUpdate', args: [newPropsWithDefaults, newState], props: initialPropsWithDefaults, state: initialState), + matchCall('componentWillReceivePropsWithContext', args: [newPropsWithDefaults, expectedContext], props: initialPropsWithDefaults, state: initialState), + matchCall('shouldComponentUpdateWithContext', args: [newPropsWithDefaults, newState, expectedContext], props: initialPropsWithDefaults, state: initialState), matchCall('componentWillUpdate', args: [newPropsWithDefaults, newState], props: initialPropsWithDefaults, state: initialState), + matchCall('componentWillUpdateWithContext', args: [newPropsWithDefaults, newState, expectedContext], props: initialPropsWithDefaults, state: initialState), matchCall('render', props: newPropsWithDefaults, state: newState), matchCall('componentDidUpdate', args: [initialPropsWithDefaults, initialState], props: newPropsWithDefaults, state: newState), + matchCall('componentDidUpdateWithContext', args: [initialPropsWithDefaults, initialState, expectedContext], props: newPropsWithDefaults, state: newState), ])); }); - group('when shouldComponentUpdate returns false:', () { + void testShouldUpdates({bool shouldComponentUpdateWithContext, bool shouldComponentUpdate}) { test('recieves updated props with correct lifecycle calls and does not rerender', () { + final Map expectedContext = const {}; final Map initialProps = unmodifiableMap({ - 'shouldComponentUpdate': (_, __, ___) => false, + 'shouldComponentUpdate': (_, __, ___) => shouldComponentUpdate, + 'shouldComponentUpdateWithContext': (_, __, ___, ____) => shouldComponentUpdateWithContext, 'initialProp': 'initial', 'children': const [] }); @@ -291,14 +305,24 @@ void main() { react_dom.render(LifecycleTest(newProps), mountNode); - expect(component.lifecycleCalls, equals([ + List calls = [ matchCall('componentWillReceiveProps', args: [newPropsWithDefaults], props: initialPropsWithDefaults), - matchCall('shouldComponentUpdate', args: [newPropsWithDefaults, expectedState], props: initialPropsWithDefaults), - ])); + matchCall('componentWillReceivePropsWithContext', args: [newPropsWithDefaults, expectedContext], props: initialPropsWithDefaults), + matchCall('shouldComponentUpdateWithContext', args: [newPropsWithDefaults, expectedState, expectedContext], props: initialPropsWithDefaults), + ]; + + if (shouldComponentUpdateWithContext == null) { + calls.add( + matchCall('shouldComponentUpdate', args: [newPropsWithDefaults, expectedState], props: initialPropsWithDefaults), + ); + } + + expect(component.lifecycleCalls, equals(calls)); expect(component.props, equals(newPropsWithDefaults)); }); test('updates state with correct lifecycle calls and does not rerender', () { + const Map expectedContext = const {}; const Map initialState = const { 'initialState': 'initial', }; @@ -312,7 +336,8 @@ void main() { final Map initialProps = unmodifiableMap({ 'getInitialState': (_) => initialState, - 'shouldComponentUpdate': (_, __, ___) => false, + 'shouldComponentUpdate': (_, __, ___) => shouldComponentUpdate, + 'shouldComponentUpdateWithContext': (_, __, ___, ____) => shouldComponentUpdateWithContext, }); final Map expectedProps = unmodifiableMap( @@ -324,9 +349,17 @@ void main() { component.setState(stateDelta); - expect(component.lifecycleCalls, equals([ - matchCall('shouldComponentUpdate', args: [expectedProps, newState], state: initialState), - ])); + List calls = [ + matchCall('shouldComponentUpdateWithContext', args: [expectedProps, newState, expectedContext], state: initialState), + ]; + + if (shouldComponentUpdateWithContext == null) { + calls.add( + matchCall('shouldComponentUpdate', args: [expectedProps, newState], state: initialState), + ); + } + + expect(component.lifecycleCalls, equals(calls)); expect(component.state, equals(newState)); }); @@ -343,7 +376,8 @@ void main() { }; final Map lifecycleTestProps = unmodifiableMap({ - 'shouldComponentUpdate': (_, __, ___) => false, + 'shouldComponentUpdate': (_, __, ___) => shouldComponentUpdate, + 'shouldComponentUpdateWithContext': (_, __, ___, ____) => shouldComponentUpdateWithContext, 'getInitialState': (_) => initialState, 'componentWillReceiveProps': (_LifecycleTest component, Map props) { component.setState(stateDelta); @@ -363,6 +397,8 @@ void main() { defaultProps, newProps, emptyChildrenProps ); + final Map expectedContext = const {}; + var mountNode = new DivElement(); var instance = react_dom.render(LifecycleTest(initialProps), mountNode); _LifecycleTest component = getDartComponent(instance); @@ -371,11 +407,28 @@ void main() { react_dom.render(LifecycleTest(newProps), mountNode); - expect(component.lifecycleCalls, equals([ - matchCall('componentWillReceiveProps', args: [newPropsWithDefaults], props: initialPropsWithDefaults, state: initialState), - matchCall('shouldComponentUpdate', args: [newPropsWithDefaults, newState], props: initialPropsWithDefaults, state: initialState), - ])); + List calls = [ + matchCall('componentWillReceiveProps', args: [newPropsWithDefaults], props: initialPropsWithDefaults, state: initialState), + matchCall('componentWillReceivePropsWithContext', args: [newPropsWithDefaults, expectedContext], props: initialPropsWithDefaults, state: initialState), + matchCall('shouldComponentUpdateWithContext', args: [newPropsWithDefaults, newState, expectedContext], props: initialPropsWithDefaults, state: initialState), + ]; + + if (shouldComponentUpdateWithContext == null) { + calls.add( + matchCall('shouldComponentUpdate', args: [newPropsWithDefaults, newState], props: initialPropsWithDefaults, state: initialState), + ); + } + + expect(component.lifecycleCalls, equals(calls)); }); + } + + group('when shouldComponentUpdate returns false:', () { + testShouldUpdates(shouldComponentUpdateWithContext: null, shouldComponentUpdate: false); + }); + + group('when shouldComponentUpdateWithContext returns false:', () { + testShouldUpdates(shouldComponentUpdateWithContext: false, shouldComponentUpdate: false); }); group('calls the setState callback, and transactional setState callback in the correct order', () { @@ -440,6 +493,11 @@ class _SetStateTest extends react.Component { recordLifecyleCall('componentWillReceiveProps'); } + @override + componentWillReceivePropsWithContext(_, __) { + recordLifecyleCall('componentWillReceivePropsWithContext'); + } + @override componentWillUpdate(_, __) { recordLifecyleCall('componentWillUpdate'); From 846c0f11872678732524eb2cefb2f3f9d7bddea2 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Thu, 2 Nov 2017 15:44:13 -0500 Subject: [PATCH 18/31] Update dart_helpers comment --- js_src/dart_helpers.js | 2 +- lib/react.js | 2 +- lib/react_prod.js | 2 +- lib/react_with_addons.js | 2 +- lib/react_with_react_dom_prod.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js_src/dart_helpers.js b/js_src/dart_helpers.js index 2819cef8..80cfc607 100644 --- a/js_src/dart_helpers.js +++ b/js_src/dart_helpers.js @@ -49,7 +49,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; } - // Only declare this when `hasChildContext` is true to avoid unnecessarily + // Only declare this when `childContextKeys` is non-empty to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { return dartInteropStatics.handleGetChildContext(this.dartComponent); diff --git a/lib/react.js b/lib/react.js index 3f26bc1a..ee40e64d 100644 --- a/lib/react.js +++ b/lib/react.js @@ -4368,7 +4368,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; } - // Only declare this when `hasChildContext` is true to avoid unnecessarily + // Only declare this when `childContextKeys` is non-empty to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { return dartInteropStatics.handleGetChildContext(this.dartComponent); diff --git a/lib/react_prod.js b/lib/react_prod.js index 718358c7..9f1a0952 100644 --- a/lib/react_prod.js +++ b/lib/react_prod.js @@ -60,7 +60,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; } - // Only declare this when `hasChildContext` is true to avoid unnecessarily + // Only declare this when `childContextKeys` is non-empty to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { return dartInteropStatics.handleGetChildContext(this.dartComponent); diff --git a/lib/react_with_addons.js b/lib/react_with_addons.js index 46e5cc6f..01337889 100644 --- a/lib/react_with_addons.js +++ b/lib/react_with_addons.js @@ -6009,7 +6009,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; } - // Only declare this when `hasChildContext` is true to avoid unnecessarily + // Only declare this when `childContextKeys` is non-empty to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { return dartInteropStatics.handleGetChildContext(this.dartComponent); diff --git a/lib/react_with_react_dom_prod.js b/lib/react_with_react_dom_prod.js index b2fbb6bc..adae4484 100644 --- a/lib/react_with_react_dom_prod.js +++ b/lib/react_with_react_dom_prod.js @@ -60,7 +60,7 @@ function _createReactDartComponentClassConfig(dartInteropStatics, componentStati config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; } - // Only declare this when `hasChildContext` is true to avoid unnecessarily + // Only declare this when `childContextKeys` is non-empty to avoid unnecessarily // creating interop context objects for components that won't use it. config.getChildContext = function() { return dartInteropStatics.handleGetChildContext(this.dartComponent); From 6e313053d59ae4becd308abc00c7fdc730937509 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Thu, 30 Nov 2017 17:34:21 -0600 Subject: [PATCH 19/31] Clean-up / Removed componentDidUpdateWithContext --- lib/react.dart | 31 ++++++------------------------ lib/react_client.dart | 4 +--- test/ReactSetStateTestComponent.js | 4 ---- test/lifecycle_test.dart | 6 ------ 4 files changed, 7 insertions(+), 38 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index 58b0ce92..c186aea2 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -99,9 +99,9 @@ abstract class Component { /// Initializes context _initContext(context) { - this.context = new Map.from(context ?? {}); + this.context = new Map.from(context ?? const {}); - /// Call `transferComponentContext` to get state also to `_prevContext` + // Call `transferComponentContext` to get state also to `_prevContext` transferComponentContext(); } @@ -113,14 +113,13 @@ abstract class Component { initStateInternal() { this.state = new Map.from(getInitialState()); - /// Call `transferComponentState` to get state also to `_prevState` + // Call `transferComponentState` to get state also to `_prevState` transferComponentState(); } /// Private reference to the value of [context] for the upcoming render cycle. /// - /// Useful for ReactJS lifecycle methods [shouldComponentUpdateWithContext], [componentWillUpdateWithContext] and - /// [componentDidUpdateWithContext]. + /// Useful for ReactJS lifecycle methods [shouldComponentUpdateWithContext] and [componentWillUpdateWithContext]. Map _nextContext = null; /// Private reference to the value of [state] for the upcoming render cycle. @@ -129,9 +128,7 @@ abstract class Component { Map _nextState = null; /// Reference to the value of [context] from the previous render cycle, used internally for proxying - /// the ReactJS lifecycle method and [componentDidUpdateWithContext]. - /// - /// Not available after [componentDidUpdateWithContext] is called. + /// the ReactJS lifecycle method. /// /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. Map prevContext; @@ -144,7 +141,7 @@ abstract class Component { /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. Map prevState; - /// Public getter for [_nextState]. + /// Public getter for [_nextContext]. /// /// If `null`, then [_nextContext] is equal to [context] - which is the value that will be returned. Map get nextContext => _nextContext == null ? context : _nextContext; @@ -328,25 +325,9 @@ 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]. /// - /// __Note__: Choose either this method or [componentDidUpdateWithContext]. They are both called at the same time so - /// using both provides no added benefit. - /// /// See: void componentDidUpdate(Map prevProps, Map prevState) {} - /// ReactJS lifecycle method that is invoked immediately after the `Component`'s updates are flushed to the DOM. - /// - /// This method is not called for the initial [render]. - /// - /// 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] /[prevContext]. - /// - /// __Note__: Choose either this method or [componentDidUpdate]. They are both called at the same time so using both - /// provides no added benefit. - /// - /// See: - void componentDidUpdateWithContext(Map prevProps, Map prevState, Map prevContext) {} - /// 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 diff --git a/lib/react_client.dart b/lib/react_client.dart index 2ba95b9a..4c68d2e5 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -331,9 +331,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { var prevInternalProps = prevInternal.props; /// Call `componentDidUpdate` and the context variant - component - ..componentDidUpdate(prevInternalProps, component.prevState) - ..componentDidUpdateWithContext(prevInternalProps, component.prevState, component.prevContext); + component.componentDidUpdate(prevInternalProps, component.prevState); _callSetStateCallbacks(component); // Clear out prevState after it's done being used so it's not retained diff --git a/test/ReactSetStateTestComponent.js b/test/ReactSetStateTestComponent.js index 668400bf..5c3b7728 100644 --- a/test/ReactSetStateTestComponent.js +++ b/test/ReactSetStateTestComponent.js @@ -53,10 +53,6 @@ var ReactSetStateTestComponent = React.createClass({ this.recordLifecyleCall("componentDidUpdate"); }, - componentDidUpdateWithContext: function(_, __, ___) { - this.recordLifecyleCall("componentDidUpdateWithContext"); - }, - outerSetStateCallback: function() { this.recordLifecyleCall('outerSetStateCallback'); }, diff --git a/test/lifecycle_test.dart b/test/lifecycle_test.dart index 97556b60..ad3c5fb1 100644 --- a/test/lifecycle_test.dart +++ b/test/lifecycle_test.dart @@ -177,7 +177,6 @@ void main() { matchCall('componentWillUpdateWithContext', args: [newPropsWithDefaults, expectedState, expectedContext], props: initialPropsWithDefaults), matchCall('render', props: newPropsWithDefaults), matchCall('componentDidUpdate', args: [initialPropsWithDefaults, expectedState], props: newPropsWithDefaults), - matchCall('componentDidUpdateWithContext', args: [initialPropsWithDefaults, expectedState, expectedContext], props: newPropsWithDefaults), ])); }); @@ -217,7 +216,6 @@ void main() { matchCall('componentWillUpdateWithContext', args: [expectedProps, newState, newContext], state: initialState), matchCall('render', state: newState), matchCall('componentDidUpdate', args: [expectedProps, initialState], state: newState), - matchCall('componentDidUpdateWithContext', args: [expectedProps, initialState, newContext], state: newState), ])); }); @@ -270,7 +268,6 @@ void main() { matchCall('componentWillUpdateWithContext', args: [newPropsWithDefaults, newState, expectedContext], props: initialPropsWithDefaults, state: initialState), matchCall('render', props: newPropsWithDefaults, state: newState), matchCall('componentDidUpdate', args: [initialPropsWithDefaults, initialState], props: newPropsWithDefaults, state: newState), - matchCall('componentDidUpdateWithContext', args: [initialPropsWithDefaults, initialState, expectedContext], props: newPropsWithDefaults, state: newState), ])); }); @@ -616,9 +613,6 @@ class _LifecycleTest extends react.Component { void componentDidUpdate(prevProps, prevState) => lifecycleCall('componentDidUpdate', arguments: [new Map.from(prevProps), new Map.from(prevState)]); - void componentDidUpdateWithContext(prevProps, prevState, prevContext) => - lifecycleCall('componentDidUpdateWithContext', arguments: [new Map.from(prevProps), new Map.from(prevState), new Map.from(prevContext)]); - bool shouldComponentUpdate(nextProps, nextState) => lifecycleCall('shouldComponentUpdate', arguments: [new Map.from(nextProps), new Map.from(nextState)], defaultReturnValue: () => true); From 61c6609fc95d380d2d942c71005df216124af78f Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Thu, 30 Nov 2017 17:34:30 -0600 Subject: [PATCH 20/31] Added testing steps to README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 11f19db4..c43080b6 100644 --- a/README.md +++ b/README.md @@ -256,3 +256,10 @@ void main() { ``` To test the Dart wrapper, take a look at [test/react_test_utils_test.dart](test). + +## Contributing + +### Running Tests + +Dart VM: `pub run test -p content-shell` +dart2js: `pub run test -p chrome` From 7861c20a176af4424e6df2e5b0b77a0e31725be3 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Thu, 30 Nov 2017 20:28:25 -0600 Subject: [PATCH 21/31] Update example to showcase state updates --- example/test/react_test_components.dart | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/example/test/react_test_components.dart b/example/test/react_test_components.dart index 92978da7..69a395fc 100644 --- a/example/test/react_test_components.dart +++ b/example/test/react_test_components.dart @@ -154,27 +154,29 @@ class _MainComponent extends react.Component { var mainComponent = react.registerComponent(() => new _MainComponent()); class _ContextComponent extends react.Component { - int _renderCount = 0; + @override + Iterable get childContextKeys => const ['foo', 'bar', 'renderCount']; @override Map getChildContext() => { 'foo': {'object': 'with value'}, 'bar': true, - 'renderCount': _renderCount + 'renderCount': this.state['renderCount'] }; - @override - Iterable get childContextKeys => const ['foo', 'bar', 'renderCount']; - render() { - _renderCount++; - return react.ul({}, + react.button({'onClick': _onButtonClick}, 'Redraw'), + react.br({}), 'ContextComponent.getChildContext(): ', getChildContext().toString(), props['children'] ); } + + _onButtonClick(event) { + this.setState({'renderCount': (this.state['renderCount'] ?? 0) + 1}); + } } var contextComponent = react.registerComponent(() => new _ContextComponent()); @@ -182,6 +184,9 @@ class _ContextConsumerComponent extends react.Component { @override Iterable get contextKeys => const ['foo', 'renderCount']; + @override + Iterable get childContextKeys => const ['foo', 'renderCount']; + render() { return react.ul({}, 'ContextConsumerComponent.context: ', From 804ac5eecd5c9fe496d358db1a7b361bef1c308e Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Thu, 30 Nov 2017 20:36:42 -0600 Subject: [PATCH 22/31] Use context directly vs transferring context --- lib/react.dart | 19 +++++-------------- lib/react_client.dart | 25 +++++++++---------------- lib/react_server.dart | 3 --- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index c186aea2..afb9e09d 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -100,9 +100,7 @@ abstract class Component { /// Initializes context _initContext(context) { this.context = new Map.from(context ?? const {}); - - // Call `transferComponentContext` to get state also to `_prevContext` - transferComponentContext(); + this.nextContext = this.context; } _initProps(props) { @@ -120,7 +118,7 @@ abstract class Component { /// Private reference to the value of [context] for the upcoming render cycle. /// /// Useful for ReactJS lifecycle methods [shouldComponentUpdateWithContext] and [componentWillUpdateWithContext]. - Map _nextContext = null; + Map nextContext; /// Private reference to the value of [state] for the upcoming render cycle. /// @@ -144,7 +142,7 @@ abstract class Component { /// Public getter for [_nextContext]. /// /// If `null`, then [_nextContext] is equal to [context] - which is the value that will be returned. - Map get nextContext => _nextContext == null ? context : _nextContext; + // Map get nextContext => _nextContext == null ? context : _nextContext; /// Public getter for [_nextState]. /// @@ -159,15 +157,6 @@ abstract class Component { /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. Map nextProps; - /// Transfers `Component` [_nextContext] to [context], and [context] to [prevContext]. - void transferComponentContext() { - prevContext = context; - if (_nextContext != null) { - context = _nextContext; - } - _nextContext = new Map.from(context); - } - /// Transfers `Component` [_nextState] to [state], and [state] to [prevState]. void transferComponentState() { prevState = state; @@ -183,6 +172,8 @@ abstract class Component { /// /// [A.k.a "forceUpdate"](https://facebook.github.io/react/docs/react-component.html#forceupdate) void redraw([callback()]) { + context = getChildContext(); + setState({}, callback); } diff --git a/lib/react_client.dart b/lib/react_client.dart index 4c68d2e5..6d66d38f 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -194,7 +194,7 @@ Map _unjsifyContext(InteropContextValue interopContext) { // TODO consider using `contextKeys` for this if perf of objectKeys is bad. return new Map.fromIterable(_objectKeys(interopContext), value: (key) { ReactDartContextInternal internal = getProperty(interopContext, key); - return internal.value; + return internal?.value; }); } @@ -233,8 +233,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { void handleComponentWillMount(Component component) => zone.run(() { component ..componentWillMount() - ..transferComponentState() - ..transferComponentContext(); + ..transferComponentState(); }); /// Wrapper for [Component.componentDidMount]. @@ -249,16 +248,14 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// 1. Update [Component.props] using the value stored to [Component.nextProps] /// in `componentWillReceiveProps`. - /// 2. Update [Component.state] by calling [Component.transferComponentState] - /// 3. Update [Component.context] by calling [Component.transferComponentContext] + /// 2. Update [Component.context] using the value stored to [Component.nextContext] + /// in `componentWillReceivePropsWithContext`. + /// 3. Update [Component.state] by calling [Component.transferComponentState] void _afterPropsChange(Component component, InteropContextValue nextContext) { - component.props = component.nextProps; // [1] - component.transferComponentState(); // [2] - component.transferComponentContext(); // [3] - } - - void _clearPrevContext(Component component) { - component.prevContext = null; + component + ..props = component.nextProps // [1] + ..context = _unjsifyContext(nextContext) // [2] + ..transferComponentState(); // [3] } void _clearPrevState(Component component) { @@ -308,8 +305,6 @@ final ReactDartInteropStatics _dartInteropStatics = (() { _callSetStateCallbacks(component); // Clear out prevState after it's done being used so it's not retained _clearPrevState(component); - // Clear out prevContext after it's done being used so it's not retained - _clearPrevContext(component); return false; } }); @@ -336,8 +331,6 @@ final ReactDartInteropStatics _dartInteropStatics = (() { _callSetStateCallbacks(component); // Clear out prevState after it's done being used so it's not retained _clearPrevState(component); - // Clear out prevContext after it's done being used so it's not retained - _clearPrevContext(component); }); /// Wrapper for [Component.componentWillUnmount]. diff --git a/lib/react_server.dart b/lib/react_server.dart index c0fe2f83..62074920 100644 --- a/lib/react_server.dart +++ b/lib/react_server.dart @@ -45,9 +45,6 @@ ReactComponentFactory _registerComponent(ComponentFactory componentFactory, [Ite // If componentWillMount called setState or replaceState, then transfer this state to actual state. component.transferComponentState(); - // If componentWillMount updated context, then transfer this context to actual context. - component.transferComponentContext(); - // Return result of component.render() return ([String ownerId, num position, String key]) { return component.render()(ownerId, position, key); From 64fbaae4e973ccd4855153eb395f764f5ff5e998 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Thu, 30 Nov 2017 20:36:50 -0600 Subject: [PATCH 23/31] Add JS build to README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index c43080b6..b8c86929 100644 --- a/README.md +++ b/README.md @@ -263,3 +263,10 @@ To test the Dart wrapper, take a look at [test/react_test_utils_test.dart](test) Dart VM: `pub run test -p content-shell` dart2js: `pub run test -p chrome` + +### Build JS + +After modifying dart_helpers.js, run: +``` +$ tool/build_js.sh +``` From 5f4d403ae2cf4d4d8887c78a270665e8a06316c1 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Thu, 30 Nov 2017 20:43:45 -0600 Subject: [PATCH 24/31] recieves -> receives --- test/lifecycle_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/lifecycle_test.dart b/test/lifecycle_test.dart index ad3c5fb1..7fba0a14 100644 --- a/test/lifecycle_test.dart +++ b/test/lifecycle_test.dart @@ -112,7 +112,7 @@ void main() { }; } - test('recieves correct lifecycle calls on component mount', () { + test('receives correct lifecycle calls on component mount', () { _LifecycleTest component = getDartComponent( render(LifecycleTest({})) ); @@ -125,7 +125,7 @@ void main() { ])); }); - test('recieves correct lifecycle calls on component unmount order', () { + test('receives correct lifecycle calls on component unmount order', () { var mountNode = new DivElement(); var instance = react_dom.render(LifecycleTest({}), mountNode); _LifecycleTest component = getDartComponent(instance); @@ -139,7 +139,7 @@ void main() { ])); }); - test('recieves updated props with correct lifecycle calls and defaults properly merged in', () { + test('receives updated props with correct lifecycle calls and defaults properly merged in', () { const Map initialProps = const { 'initialProp': 'initial', 'children': const [] @@ -272,7 +272,7 @@ void main() { }); void testShouldUpdates({bool shouldComponentUpdateWithContext, bool shouldComponentUpdate}) { - test('recieves updated props with correct lifecycle calls and does not rerender', () { + test('receives updated props with correct lifecycle calls and does not rerender', () { final Map expectedContext = const {}; final Map initialProps = unmodifiableMap({ 'shouldComponentUpdate': (_, __, ___) => shouldComponentUpdate, From dc0e5af36f3b2593c02b96d0b3197e42a27062fe Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Thu, 30 Nov 2017 20:50:03 -0600 Subject: [PATCH 25/31] Updated example component to show context updates to grandchildren --- example/test/context_test.dart | 2 +- example/test/react_test_components.dart | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/example/test/context_test.dart b/example/test/context_test.dart index bf94d844..fedcb020 100644 --- a/example/test/context_test.dart +++ b/example/test/context_test.dart @@ -17,6 +17,6 @@ void main() { ), querySelector('#content')); react_dom.render(contextComponent({}, - contextConsumerComponent({}, fooOnlyContextConsumerComponent({})), + contextConsumerComponent({}, grandchildContextConsumerComponent({})), ), querySelector('#content')); } diff --git a/example/test/react_test_components.dart b/example/test/react_test_components.dart index 69a395fc..cf4a2c4a 100644 --- a/example/test/react_test_components.dart +++ b/example/test/react_test_components.dart @@ -170,6 +170,8 @@ class _ContextComponent extends react.Component { react.br({}), 'ContextComponent.getChildContext(): ', getChildContext().toString(), + react.br({}), + react.br({}), props['children'] ); } @@ -182,30 +184,29 @@ var contextComponent = react.registerComponent(() => new _ContextComponent()); class _ContextConsumerComponent extends react.Component { @override - Iterable get contextKeys => const ['foo', 'renderCount']; - - @override - Iterable get childContextKeys => const ['foo', 'renderCount']; + Iterable get contextKeys => const ['foo']; render() { return react.ul({}, 'ContextConsumerComponent.context: ', context.toString(), + react.br({}), + react.br({}), props['children'] ); } } var contextConsumerComponent = react.registerComponent(() => new _ContextConsumerComponent()); -class _FooOnlyContextConsumerComponent extends react.Component { +class _GrandchildContextConsumerComponent extends react.Component { @override - Iterable get contextKeys => const ['foo']; + Iterable get contextKeys => const ['renderCount']; render() { return react.ul({}, - 'FooOnlyContextConsumerComponent.context: ', + 'GrandchildContextConsumerComponent.context: ', context.toString(), ); } } -var fooOnlyContextConsumerComponent = react.registerComponent(() => new _FooOnlyContextConsumerComponent()); +var grandchildContextConsumerComponent = react.registerComponent(() => new _GrandchildContextConsumerComponent()); From 92fbc5321074b9eefbff29280007931b66cb2720 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 1 Dec 2017 17:31:22 -0600 Subject: [PATCH 26/31] Fix lifecycle calls and add test coverage --- lib/react_client.dart | 6 +-- test/lifecycle_test.dart | 94 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/lib/react_client.dart b/lib/react_client.dart index 6d66d38f..69c6819b 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -281,7 +281,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { component ..nextProps = nextProps ..componentWillReceiveProps(nextProps) - ..componentWillReceivePropsWithContext(nextProps, component.nextContext); + ..componentWillReceivePropsWithContext(nextProps, _unjsifyContext(nextContext)); }); /// Wrapper for [Component.shouldComponentUpdate]. @@ -291,7 +291,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { // If shouldComponentUpdateWithContext returns a valid bool (default implementation returns null), // then don't bother calling `shouldComponentUpdate` and have it trump. bool shouldUpdate = - component.shouldComponentUpdateWithContext(component.nextProps, component.nextState, component.nextContext); + component.shouldComponentUpdateWithContext(component.nextProps, component.nextState, _unjsifyContext(nextContext)); if (shouldUpdate == null) { shouldUpdate = component.shouldComponentUpdate(component.nextProps, component.nextState); @@ -314,7 +314,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// Call `componentWillUpdate` and the context variant component ..componentWillUpdate(component.nextProps, component.nextState) - ..componentWillUpdateWithContext(component.nextProps, component.nextState, component.nextContext); + ..componentWillUpdateWithContext(component.nextProps, component.nextState, _unjsifyContext(nextContext)); _afterPropsChange(component, nextContext); }); diff --git a/test/lifecycle_test.dart b/test/lifecycle_test.dart index 7fba0a14..d5f10890 100644 --- a/test/lifecycle_test.dart +++ b/test/lifecycle_test.dart @@ -103,12 +103,13 @@ void main() { 'children': const [] }; - Map matchCall(String memberName, {args: anything, props: anything, state: anything}) { + Map matchCall(String memberName, {args: anything, props: anything, state: anything, context: anything}) { return { 'memberName': memberName, 'arguments': args, 'props': props, 'state': state, + 'context': context, }; } @@ -139,6 +140,77 @@ void main() { ])); }); + test('receives updated context with correct lifecycle calls', () { + _LifecycleTestWithContext component; + + Map initialProps = { + 'foo': false, + 'initialProp': 'initial', + 'children': const [] + }; + Map newProps = { + 'children': const [], + 'foo': true, + 'newProp': 'new', + }; + + final Map initialPropsWithDefaults = unmodifiableMap({} + ..addAll(defaultProps) + ..addAll(initialProps) + ); + final Map newPropsWithDefaults = unmodifiableMap({} + ..addAll(defaultProps) + ..addAll(newProps) + ); + + const Map expectedState = const {}; + + const Map initialContext = const { + 'foo': false + }; + + const Map expectedContext = const { + 'foo': true + }; + + Map refMap = { + 'ref': ((ref) => component = ref), + }; + + // Add the 'ref' prop separately so it isn't an expected prop since React removes it internally + var initialPropsWithRef = new Map.from(initialProps)..addAll(refMap); + var newPropsWithRef = new Map.from(newPropsWithDefaults)..addAll(refMap); + + // Render the initial instance + var mountNode = new DivElement(); + react_dom.render(ContextWrapper({'foo': false}, LifecycleTestWithContext(initialPropsWithRef)), mountNode); + + // Verify initial context/setup + expect(component.lifecycleCalls, equals([ + matchCall('getInitialState', props: initialPropsWithDefaults, context: initialContext), + matchCall('componentWillMount', props: initialPropsWithDefaults, context: initialContext), + matchCall('render', props: initialPropsWithDefaults, context: initialContext), + matchCall('componentDidMount', props: initialPropsWithDefaults, context: initialContext), + ])); + + // Clear the lifecycle calls for to not duplicate the initial calls below + component.lifecycleCalls.clear(); + + // Trigger a re-render with new content + react_dom.render(ContextWrapper({'foo': true}, LifecycleTestWithContext(newPropsWithRef)), mountNode); + + // Verify updated context/setup + expect(component.lifecycleCalls, equals([ + matchCall('componentWillReceiveProps', args: [newPropsWithDefaults], props: initialPropsWithDefaults, context: initialContext), + matchCall('componentWillReceivePropsWithContext', args: [newPropsWithDefaults, expectedContext], props: initialPropsWithDefaults, context: initialContext), + matchCall('shouldComponentUpdateWithContext', args: [newPropsWithDefaults, expectedState, expectedContext], props: initialPropsWithDefaults, context: initialContext), + matchCall('componentWillUpdate', args: [newPropsWithDefaults, expectedState], props: initialPropsWithDefaults, context: initialContext), + matchCall('componentWillUpdateWithContext', args: [newPropsWithDefaults, expectedState, expectedContext], props: initialPropsWithDefaults, context: initialContext), + matchCall('render', props: newPropsWithDefaults, context: expectedContext), + matchCall('componentDidUpdate', args: [initialPropsWithDefaults, expectedState], props: newPropsWithDefaults, context: expectedContext), + ])); + }); + test('receives updated props with correct lifecycle calls and defaults properly merged in', () { const Map initialProps = const { 'initialProp': 'initial', @@ -568,6 +640,25 @@ class _DefaultPropsTest extends react.Component { render() => false; } +ReactDartComponentFactoryProxy ContextWrapper = react.registerComponent(() => new _ContextWrapper()); +class _ContextWrapper extends react.Component { + @override + Iterable get childContextKeys => const ['foo']; + + @override + Map getChildContext() => { + 'foo': props['foo'] + }; + + dynamic render() => react.div({}, props['children']); +} + +ReactDartComponentFactoryProxy LifecycleTestWithContext = react.registerComponent(() => new _LifecycleTestWithContext()); +class _LifecycleTestWithContext extends _LifecycleTest { + @override + Iterable get contextKeys => const ['foo']; +} + ReactDartComponentFactoryProxy LifecycleTest = react.registerComponent(() => new _LifecycleTest()); class _LifecycleTest extends react.Component { List lifecycleCalls = []; @@ -578,6 +669,7 @@ class _LifecycleTest extends react.Component { 'arguments': arguments, 'props': props == null ? null : new Map.from(props), 'state': state == null ? null : new Map.from(state), + 'context': new Map.from(context ?? const {}), }); var lifecycleCallback = props == null ? null : props[memberName]; From 723caa5236f1e0a1dd91d2dd48983ce0bc25a0bf Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Tue, 5 Dec 2017 13:03:41 -0600 Subject: [PATCH 27/31] Remove forced context update --- lib/react.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index afb9e09d..d084933c 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -172,8 +172,6 @@ abstract class Component { /// /// [A.k.a "forceUpdate"](https://facebook.github.io/react/docs/react-component.html#forceupdate) void redraw([callback()]) { - context = getChildContext(); - setState({}, callback); } From f791d0d394558c3f2d744d081782d68170db8eaf Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Tue, 5 Dec 2017 15:58:16 -0600 Subject: [PATCH 28/31] Documentation updates --- lib/react.dart | 5 ----- lib/react_client/react_interop.dart | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/react.dart b/lib/react.dart index d084933c..eaad263a 100644 --- a/lib/react.dart +++ b/lib/react.dart @@ -139,11 +139,6 @@ abstract class Component { /// __DO NOT set__ from anywhere outside react-dart lifecycle internals. Map prevState; - /// Public getter for [_nextContext]. - /// - /// If `null`, then [_nextContext] is equal to [context] - which is the value that will be returned. - // Map get nextContext => _nextContext == null ? context : _nextContext; - /// Public getter for [_nextState]. /// /// If `null`, then [_nextState] is equal to [state] - which is the value that will be returned. diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index 10296df5..b9e3d170 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -192,7 +192,8 @@ class InteropProps { external factory InteropProps({ReactDartComponentInternal internal, String key, dynamic ref}); } -/// A Dart object that stores . +/// Internal react-dart information used to proxy React JS lifecycle to Dart +/// [Component] instances. /// /// __For internal/advanced use only.__ class ReactDartComponentInternal { From f0d8b3a4c28c62ecc25029b237d037fd4a7fab9d Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Tue, 5 Dec 2017 15:58:42 -0600 Subject: [PATCH 29/31] =?UTF-8?q?Use=20nextContext=20vs=20unjsify=E2=80=99?= =?UTF-8?q?ing=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/react_client.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/react_client.dart b/lib/react_client.dart index 69c6819b..bf4814f1 100644 --- a/lib/react_client.dart +++ b/lib/react_client.dart @@ -253,9 +253,9 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// 3. Update [Component.state] by calling [Component.transferComponentState] void _afterPropsChange(Component component, InteropContextValue nextContext) { component - ..props = component.nextProps // [1] - ..context = _unjsifyContext(nextContext) // [2] - ..transferComponentState(); // [3] + ..props = component.nextProps // [1] + ..context = component.nextContext // [2] + ..transferComponentState(); // [3] } void _clearPrevState(Component component) { @@ -278,10 +278,13 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// Wrapper for [Component.componentWillReceiveProps]. void handleComponentWillReceiveProps(Component component, ReactDartComponentInternal nextInternal, InteropContextValue nextContext) => zone.run(() { var nextProps = _getNextProps(component, nextInternal); + var newContext = _unjsifyContext(nextContext); + component ..nextProps = nextProps + ..nextContext = newContext ..componentWillReceiveProps(nextProps) - ..componentWillReceivePropsWithContext(nextProps, _unjsifyContext(nextContext)); + ..componentWillReceivePropsWithContext(nextProps, newContext); }); /// Wrapper for [Component.shouldComponentUpdate]. @@ -291,7 +294,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { // If shouldComponentUpdateWithContext returns a valid bool (default implementation returns null), // then don't bother calling `shouldComponentUpdate` and have it trump. bool shouldUpdate = - component.shouldComponentUpdateWithContext(component.nextProps, component.nextState, _unjsifyContext(nextContext)); + component.shouldComponentUpdateWithContext(component.nextProps, component.nextState, component.nextContext); if (shouldUpdate == null) { shouldUpdate = component.shouldComponentUpdate(component.nextProps, component.nextState); @@ -314,7 +317,7 @@ final ReactDartInteropStatics _dartInteropStatics = (() { /// Call `componentWillUpdate` and the context variant component ..componentWillUpdate(component.nextProps, component.nextState) - ..componentWillUpdateWithContext(component.nextProps, component.nextState, _unjsifyContext(nextContext)); + ..componentWillUpdateWithContext(component.nextProps, component.nextState, component.nextContext); _afterPropsChange(component, nextContext); }); From 282914bb5e1dd7bb6b7cbc8cd19531a515fb58bc Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Tue, 5 Dec 2017 16:33:04 -0600 Subject: [PATCH 30/31] Test updates --- test/lifecycle_test.dart | 110 +++++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 32 deletions(-) diff --git a/test/lifecycle_test.dart b/test/lifecycle_test.dart index d5f10890..c10c59b4 100644 --- a/test/lifecycle_test.dart +++ b/test/lifecycle_test.dart @@ -140,6 +140,24 @@ void main() { ])); }); + test('does not call getChildContext when childContextKeys is empty', () { + var mountNode = new DivElement(); + var instance = react_dom.render(ContextWrapperWithoutKeys({'foo': false}, LifecycleTestWithContext({})), mountNode); + _ContextWrapperWithoutKeys component = getDartComponent(instance); + + expect(component.lifecycleCalls, isEmpty); + }); + + test('calls getChildContext when childContextKeys exist', () { + var mountNode = new DivElement(); + var instance = react_dom.render(ContextWrapper({'foo': false}, LifecycleTestWithContext({})), mountNode); + _ContextWrapper component = getDartComponent(instance); + + expect(component.lifecycleCalls, equals([ + matchCall('getChildContext'), + ])); + }); + test('receives updated context with correct lifecycle calls', () { _LifecycleTestWithContext component; @@ -549,6 +567,38 @@ external List getUpdatingSetStateLifeCycleCalls(); @JS() external List getNonUpdatingSetStateLifeCycleCalls(); +/// A test helper to record lifecycle calls +abstract class LifecycleTestHelper { + Map context; + Map props; + Map state; + + List lifecycleCalls = []; + + dynamic lifecycleCall(String memberName, {List arguments: const [], defaultReturnValue()}) { + lifecycleCalls.add({ + 'memberName': memberName, + 'arguments': arguments, + 'props': props == null ? null : new Map.from(props), + 'state': state == null ? null : new Map.from(state), + 'context': new Map.from(context ?? const {}), + }); + + var lifecycleCallback = props == null ? null : props[memberName]; + if (lifecycleCallback != null) { + return Function.apply(lifecycleCallback, [] + ..add(this) + ..addAll(arguments)); + } + + if (defaultReturnValue != null) { + return defaultReturnValue(); + } + + return null; + } +} + ReactDartComponentFactoryProxy SetStateTest = react.registerComponent(() => new _SetStateTest()); class _SetStateTest extends react.Component { @override @@ -640,15 +690,36 @@ class _DefaultPropsTest extends react.Component { render() => false; } +ReactDartComponentFactoryProxy ContextWrapperWithoutKeys = react.registerComponent(() => new _ContextWrapperWithoutKeys()); +class _ContextWrapperWithoutKeys extends react.Component with LifecycleTestHelper { + @override + Iterable get childContextKeys => const []; + + @override + Map getChildContext() { + lifecycleCall('getChildContext'); + return { + 'foo': props['foo'], + 'extraContext': props['extraContext'], + }; + } + + dynamic render() => react.div({}, props['children']); +} + ReactDartComponentFactoryProxy ContextWrapper = react.registerComponent(() => new _ContextWrapper()); -class _ContextWrapper extends react.Component { +class _ContextWrapper extends react.Component with LifecycleTestHelper { @override - Iterable get childContextKeys => const ['foo']; + Iterable get childContextKeys => const ['foo', 'extraContext']; @override - Map getChildContext() => { - 'foo': props['foo'] - }; + Map getChildContext() { + lifecycleCall('getChildContext'); + return { + 'foo': props['foo'], + 'extraContext': props['extraContext'], + }; + } dynamic render() => react.div({}, props['children']); } @@ -656,36 +727,11 @@ class _ContextWrapper extends react.Component { ReactDartComponentFactoryProxy LifecycleTestWithContext = react.registerComponent(() => new _LifecycleTestWithContext()); class _LifecycleTestWithContext extends _LifecycleTest { @override - Iterable get contextKeys => const ['foo']; + Iterable get contextKeys => const ['foo']; // only listening to one context key } ReactDartComponentFactoryProxy LifecycleTest = react.registerComponent(() => new _LifecycleTest()); -class _LifecycleTest extends react.Component { - List lifecycleCalls = []; - - dynamic lifecycleCall(String memberName, {List arguments: const [], defaultReturnValue()}) { - lifecycleCalls.add({ - 'memberName': memberName, - 'arguments': arguments, - 'props': props == null ? null : new Map.from(props), - 'state': state == null ? null : new Map.from(state), - 'context': new Map.from(context ?? const {}), - }); - - var lifecycleCallback = props == null ? null : props[memberName]; - if (lifecycleCallback != null) { - return Function.apply(lifecycleCallback, [] - ..add(this) - ..addAll(arguments)); - } - - if (defaultReturnValue != null) { - return defaultReturnValue(); - } - - return null; - } - +class _LifecycleTest extends react.Component with LifecycleTestHelper { void componentWillMount() => lifecycleCall('componentWillMount'); void componentDidMount() => lifecycleCall('componentDidMount'); void componentWillUnmount() => lifecycleCall('componentWillUnmount'); From 36029636d351679163b0ec995dd377f650fe8754 Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Tue, 5 Dec 2017 16:36:20 -0600 Subject: [PATCH 31/31] Remove context test methods in JS --- test/ReactSetStateTestComponent.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/ReactSetStateTestComponent.js b/test/ReactSetStateTestComponent.js index 5c3b7728..deec9c7c 100644 --- a/test/ReactSetStateTestComponent.js +++ b/test/ReactSetStateTestComponent.js @@ -27,28 +27,15 @@ var ReactSetStateTestComponent = React.createClass({ this.recordLifecyleCall("componentWillReceiveProps"); }, - componentWillReceivePropsWithContext: function(_) { - this.recordLifecyleCall("componentWillReceivePropsWithContext"); - }, - shouldComponentUpdate: function(_, __) { this.recordLifecyleCall("shouldComponentUpdate"); return this.props.shouldUpdate; }, - shouldComponentUpdateWithContext: function(_, __, ___) { - this.recordLifecyleCall("shouldComponentUpdateWithContext"); - return this.props.shouldUpdate; - }, - componentWillUpdate: function(_, __) { this.recordLifecyleCall("componentWillUpdate"); }, - componentWillUpdateWithContext: function(_, __, ___) { - this.recordLifecyleCall("componentWillUpdateWithContext"); - }, - componentDidUpdate: function(_, __) { this.recordLifecyleCall("componentDidUpdate"); },