-
Notifications
You must be signed in to change notification settings - Fork 68
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 #135
Context support #135
Conversation
js_src/dart_helpers.js
Outdated
config.childContextTypes[childContextKeys[i]] = React.PropTypes.object; | ||
} | ||
|
||
// Only declare this when `hasChildContext` is true to avoid unnecessarily |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Referencing an old variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly. I'll dig into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so, sorry about that. Should probably read when childContextKeys is non-empty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, mainly just an issue around context updating and related test coverage.
So sorry it took me so long to take a look at this; I'll definitely be quicker to respond moving forward.
lib/react.dart
Outdated
_initProps(props); | ||
} | ||
|
||
/// Initializes context | ||
_initContext(context) { | ||
this.context = new Map.from(context ?? {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit To reduce garbage, this could be const {}
.
lib/react.dart
Outdated
_initContext(context) { | ||
this.context = new Map.from(context ?? {}); | ||
|
||
/// Call `transferComponentContext` to get state also to `_prevContext` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit This should not be a doc comment
lib/react.dart
Outdated
_initProps(props) { | ||
this.props = new Map.from(props); | ||
this.nextProps = this.props; | ||
} | ||
|
||
initStateInternal() { | ||
this.state = new Map.from(getInitialState()); | ||
// Call `transferComponentState` to get state also to `_prevState` | ||
|
||
/// Call `transferComponentState` to get state also to `_prevState` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#nit This should not be a doc comment
lib/react.dart
Outdated
@@ -114,16 +146,31 @@ abstract class Component { | |||
|
|||
/// Public getter for [_nextState]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/_nextState
/_nextContext
/
lib/react_client.dart
Outdated
component.props = component.nextProps; // [1] | ||
component.transferComponentState(); // [2] | ||
component.transferComponentContext(); // [3] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From this code, and from testing the example, context doesn't seem to ever get updated.
nextState
gets updated in setState
and then transferred to state
in transferComponentContext
, but there doesn't seem to be an analagous updating of nextContext
here.
I would recommend updating how context is managed to mirror props
as opposed to state
, since they both are updated externally.
This updating should also have test coverage (see later comment in tests).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that is a react-js issue: facebook/react#2517.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that issue is around how to handle context not getting updated when shouldComponentUpdate
returns false
.
Otherwise, context should update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I'm validating that. The initial code had this issue and I found several references of it not updating.
I'll ping back once I confirm it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Built a react-js app and it does indeed update. Investigating it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok...it is refactored to match the props integration.
lib/react.dart
Outdated
/// provides no added benefit. | ||
/// | ||
/// See: <https://facebook.github.io/react/docs/react-component.html#updating-componentdidupdate> | ||
void componentDidUpdateWithContext(Map prevProps, Map prevState, Map prevContext) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since React 16 doesn't include prevContext
in componentDidUpdate
, can we just omit this lifecycle method?
test/lifecycle_test.dart
Outdated
])); | ||
}); | ||
|
||
group('when shouldComponentUpdate returns false:', () { | ||
void testShouldUpdates({bool shouldComponentUpdateWithContext, bool shouldComponentUpdate}) { | ||
test('recieves updated props with correct lifecycle calls and does not rerender', () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be tests that context maps are updated as expected and with the correct values in the correct lifecycle methods.
An easy way to do that would be to parallel this props update test as well as the other "recieves updated props" test.
Ok, this now updates context as expected. It isn't yet unit tested, but the big hurdle is done. Gotta jet. I'll tighten this up tomorrow. |
Ok, @greglittlefield-wf...final review time. That test found some issues w/ the wrong context being passed to lifecycle methods. Let me know how it sits. |
lib/react.dart
Outdated
@@ -139,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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this happen here but not in setState
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrmm...guess I mistook JS for Dart here w/ the _jsRedraw
vs redraw
.
Should it go in replaceState
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, what's the purpose of doing this anyways? Doesn't this https://github.com/cleandart/react-dart/pull/135/files#diff-12ac054bb8b76f35c1035afef1d94396R257 update the context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great find. I removed it and all tests pass + examples work. See 723caa5.
+1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One comment on internal context management, some small stuff, and a couple areas where test coverage could be improved.
Otherwise, looks great!! This is really close!
lib/react.dart
Outdated
/// 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dangling doc comment. Looks like this should be the doc comment for nextContext
whose current comment doesn't seem to match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, that was dead code from a previous build. Removing...
void _afterPropsChange(Component component) { | ||
component.props = component.nextProps; // [1] | ||
component.transferComponentState(); // [2] | ||
/// 2. Update [Component.context] using the value stored to [Component.nextContext] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment doesn't patch up with what's being done.
Also, in handleComponentWillReceiveProps
, why isn't nextContext
being stored onto the component like props is? It's likely better for perf to match that pattern and only call _unjsifyContext
once per rerender vs the 3 times per rerender that it's called currently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, man...too many twists and turns in here. :-D Great find. I found where the issue was w/ nextContext
being outdated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See f0d8b3a.
lib/react_client/react_interop.dart
Outdated
@@ -178,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 . |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this was leftover from my one of my commits, sorry 😬. Can you revert this doc comment change, please?
test/ReactSetStateTestComponent.js
Outdated
@@ -27,15 +27,28 @@ var ReactSetStateTestComponent = React.createClass({ | |||
this.recordLifecyleCall("componentWillReceiveProps"); | |||
}, | |||
|
|||
componentWillReceivePropsWithContext: function(_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These -WithContext
methods on the JS component won't ever get called, right? Why do they exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it was only testing so recordLifecyleCall
would record the call and could be validated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I'm following. recordLifecycleCall
won't be called since nothing is calling these methods on the JS component, right?
}); | ||
|
||
group('when shouldComponentUpdateWithContext returns false:', () { | ||
testShouldUpdates(shouldComponentUpdateWithContext: false, shouldComponentUpdate: false); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add one more test case that shouldComponentUpdate
is not called when shouldComponentUpdateWithContext
returns true
? Or is that overkill?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's pretty much what this is testing:
List calls = [
matchCall('componentWillReceiveProps', args: [newPropsWithDefaults], 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),
);
}
When shouldComponentUpdateWithContext
is null
we validate the method is called. If it was called when shouldComponentUpdateWithContext
had a value, this test would fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I just meant testing true
as a value in addition to false
.
@@ -139,7 +140,78 @@ void main() { | |||
])); | |||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a lifecycle tests that asserts that getChildContext
is not called when childContextKeys
is empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests incoming. Good call here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See 282914b.
matchCall('render', props: newPropsWithDefaults, context: expectedContext), | ||
matchCall('componentDidUpdate', args: [initialPropsWithDefaults, expectedState], props: newPropsWithDefaults, context: expectedContext), | ||
])); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome test! Could we add one more that verifies that only the keys present in childContextKeys
are passed down, though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting an extra key to pass but not having the value in the child should be enough. That's incoming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See 282914b.
Ok, @greglittlefield-wf, I think it is ready. Big props to you and @jacehensley-wf for catching those issues. |
+1 looks great! Going to pull this down and test, and also pull into some other repos for testing. |
+10 |
+1 refresh |
+ Deprecates everything that was added in Workiva#135 + New context API will be released alongside support for ReactJS 16 in version 5.0.0 of this package
Feature
Support context.
Solution
*WithContext
methods. All of these methods replace the normal lifecycle methods so both are not needed.componentDidUpdateWithContext
componentWillUpdateWithContext
shouldComponentUpdateWithContext
componentWillReceivePropsWithContext
shouldComponentUpdateWithContext
returnsnull
by default soshouldComponentUpdate
is still used. IfshouldComponentUpdateWithContext
returns a validbool
,shouldComponentUpdate
will not get checkedTesting