Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Context support (WIP) #134

Closed
wants to merge 9 commits into from
18 changes: 18 additions & 0 deletions example/test/context_test.dart
Original file line number Diff line number Diff line change
@@ -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'));
}
14 changes: 14 additions & 0 deletions example/test/context_test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>

<html>
<head>
<title>context_test</title>
</head>
<body>
<div id="content"></div>
<script src="packages/react/react.js"></script>
<script src="packages/react/react_dom.js"></script>
<script type="application/dart" src="context_test.dart"></script>
<script src="packages/browser/dart.js"></script>
</body>
</html>
38 changes: 38 additions & 0 deletions example/test/react_test_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, dynamic> getChildContext() => {
'foo': 'bar',
'renderCount': _renderCount
};

@override
Iterable<String> 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<String> get contextKeys => const ['foo', 'renderCount'];

render() {
return react.ul({},
'ContextConsumerComponent.context: ',
context.toString(),
);
}
}
var contextConsumerComponent = react.registerComponent(() => new _ContextConsumerComponent());
46 changes: 37 additions & 9 deletions js_src/dart_helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
function _getProperty(obj, key) { return obj[key]; }
function _setProperty(obj, key, value) { return obj[key] = value; }

function _createReactDartComponentClassConfig(dartInteropStatics, componentStatics) {
return {
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() {
Expand All @@ -17,14 +17,14 @@ 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);
Expand All @@ -36,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.handleGetChildContext(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) {
Expand Down
30 changes: 29 additions & 1 deletion lib/react.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -37,6 +39,12 @@ 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;

/// ReactJS [Component] props.
///
/// Related: [state]
Expand Down Expand Up @@ -81,13 +89,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(props, _jsRedraw, [Ref ref, _jsThis, context]) {
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;
Expand Down Expand Up @@ -243,6 +256,21 @@ abstract class Component {
/// See: <https://facebook.github.io/react/docs/react-component.html#unmounting-componentwillunmount>
void componentWillUnmount() {}

/// 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<String, dynamic> 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<String> get childContextKeys => const [];

/// The keys of context used by this component.
///
/// __This method is called only once, upon component registration.__
Iterable<String> get contextKeys => const [];

/// Invoked once before the `Component` is mounted. The return value will be used as the initial value of [state].
///
/// See: <https://facebook.github.io/react/docs/react-component.html#getinitialstate>
Expand Down
46 changes: 37 additions & 9 deletions lib/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -4324,10 +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) {
return {
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() {
Expand All @@ -4336,14 +4336,14 @@ 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);
Expand All @@ -4355,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.handleGetChildContext(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) {
Expand Down
Loading