Skip to content

Conversation

sydneyjodon-wk
Copy link
Collaborator

@sydneyjodon-wk sydneyjodon-wk commented Oct 30, 2019

Motivation

Support useState hook in react-dart

Changes

  • Added StateHook class
  • Added useState and useStateLazy functions
  • Wrote tests.

Please review: @kealjones-wk @aaronlademann-wf @greglittlefield-wf @joebingham-wk

@sydneyjodon-wk sydneyjodon-wk changed the base branch from 5.1.0-wip to CPLAT-4309-react-hooks October 30, 2019 20:57
Copy link
Collaborator

@kealjones-wk kealjones-wk left a comment

Choose a reason for hiding this comment

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

Just a few things :) excited for this, nice work

@aaronlademann-wf aaronlademann-wf added this to the 5.1.0 milestone Nov 4, 2019
@@ -0,0 +1,97 @@
@JS()
library hooks;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason we are making hooks a standalone public entrypoint? Any reason to not move this to lib/src/hooks.dart and then export that from lib/react.dart like we are doing for the context APIs?

Copy link
Collaborator

Choose a reason for hiding this comment

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

After going back and forth on it, I suggested this offline to makes things a little more organized as opposed to just lumping more things into react.dart. Also, since they're APIs that will only be used sometimes, I figured it made sense for them to be their own self-contained entrypoint.

But, I could be convinced to move them into react.dart.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel the same it makes sense to have them seperate but i also enjoy the convenience of getting them when importing react.dart... plus they would get tree shaken out if they weren't used right?

lib/hooks.dart Outdated
/// Updates [value] to the return value of [computeNewValue].
///
/// See: <https://reactjs.org/docs/hooks-reference.html#functional-updates>.
void setTx(T computeNewValue(T oldValue)) => _setValue(allowInterop(computeNewValue));
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does Tx represent? Does this naming convention map to an analogous pattern in the JS world?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It was mean to be "transactional", but it was clear when I had setStateTx that that wasn't clear haha...

Perhaps setWithUpdater would be better? Or even just update? But with the latter it might not be the immediately obvious the difference between set and update.

Thoughts? I think that for these kinds of naming decisions, we should gather input from more rather than fewer people. @joebingham-wk @kealjones-wk

Copy link
Collaborator

Choose a reason for hiding this comment

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

i think setWithUpdater makes a lot of sense as it correlates to component2's new setStateWithUpdater, also we never made a codemod for that did we .... should we?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I like setWithUpdater

Copy link
Collaborator

Choose a reason for hiding this comment

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

agreed, i also like, setWithUpdater

T _value;

/// The second item in the pair returned by [React.useState].
void Function(dynamic) _setValue;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe the argument here should be typed as T.

Suggested change
void Function(dynamic) _setValue;
void Function(T newValue) _setValue;

Copy link
Collaborator

Choose a reason for hiding this comment

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

You might be right... my brain hurts when thinking about Dart 2 function types haha, so I'm not sure.

Works for me so long as there are no type errors in DDC or dart2js!

Copy link
Collaborator Author

@sydneyjodon-wk sydneyjodon-wk Nov 6, 2019

Choose a reason for hiding this comment

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

Changing this type breaks the tests for setTx. I think it's because _setValue is the updater function returned by the JS useState and can take an argument of type T newValue or type T computeNewValue(T oldValue).

Copy link
Collaborator

Choose a reason for hiding this comment

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

lib/hooks.dart Outdated
/// return react.div({}, [
/// count.value,
/// react.button({'onClick': (_) => count.set(0)}, ['Reset']),
/// react.button({'onClick': (_) => count.setTx((prev) => prev + 1)}, ['+']),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this also possible without using setTx?

react.button({'onClick': (_) => count.set(count.value + 1)}, ['+']),

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right, I just wrote it that way because that was the example of functional updates in the React docs:
https://reactjs.org/docs/hooks-reference.html#functional-updates
Should I change it to set and use a different example for setTx?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think so, yeah.

Copy link
Collaborator

Choose a reason for hiding this comment

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

But we could also just make a follow-up ticket to improve the examples.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, I updated the example here

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is actually a case where you would need to use setTx.

Let's say that two setState calls were batched together:

react.button({'onClick': (_) {
  count.setTx((prev) => prev + 1);
  count.setTx((prev) => prev + 1);
}, ['+2']),

Clicking on the button would result in the value being incremented by two.

However, without setTx:

react.button({'onClick': (_) {
  count.set(count.value + 1);
  count.set(count.value + 1);
}, ['+2']),

Clicking on the button would result in the value being incremented by one, which is not expected.

Here, it would be best practice to use transactional setState calls.

React recommends using transactional state updaters where possible, and it also uses them in this corresponding example, so I think we should keep setTx here

@aaronlademann-wf aaronlademann-wf modified the milestones: 5.1.0, 5.2.0 Nov 5, 2019
Copy link
Collaborator

@greglittlefield-wf greglittlefield-wf left a comment

Choose a reason for hiding this comment

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

+10

count.value,
react.button({'onClick': (_) => count.set(0)}, ['Reset']),
react.button({
'onClick': (_) => count.setWithUpdater((prev) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

#nit it's better to avoid the state change altogether when disabled:

      'onClick': (_) {
        if (props['enabled']) {
          count.setWithUpdater((prev) => prev + 1);
        }
      },

lib/hooks.dart Outdated
/// count.value,
/// react.button({'onClick': (_) => count.set(0)}, ['Reset']),
/// react.button({
/// 'onClick': (_) => count.setWithUpdater((prev) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

#nit We should probably show avoiding the state change altogether when disabled:

      'onClick': (_) {
        if (props['enabled']) {
          count.setWithUpdater((prev) => prev + 1);
        }
      },

Or for this example, just do:

'onClick': (_) => count.setWithUpdater((prev) => prev + 1)

@kealjones-wk
Copy link
Collaborator

+10

@greglittlefield-wf
Copy link
Collaborator

+1

@greglittlefield-wf greglittlefield-wf dismissed stale reviews from kealjones-wk and aaronlademann-wf November 21, 2019 20:20

+10'd above

@greglittlefield-wf greglittlefield-wf changed the base branch from CPLAT-4309-react-hooks to 5.2.0-wip November 21, 2019 20:43
@greglittlefield-wf greglittlefield-wf merged commit 9c615f7 into 5.2.0-wip Nov 21, 2019
@greglittlefield-wf greglittlefield-wf deleted the CPLAT-8034-usestate-hook branch February 16, 2022 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants