-
Notifications
You must be signed in to change notification settings - Fork 67
CPLAT-8034 Implement/Expose useState Hook #223
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
Changes from all commits
b6bd085
0a78d12
9ee8ff8
7c7d6b8
904044b
7d0eb8d
86a7205
abc3596
f073b95
0318115
e0a36a2
eeb5c70
8bcef06
ca69247
0dd585d
37d9629
d8da3d7
1cd8b95
2f30149
ebfda89
5aa03e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,103 @@ | ||||||
@JS() | ||||||
library hooks; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 But, I could be convinced to move them into There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
|
||||||
import 'package:js/js.dart'; | ||||||
import 'package:react/react.dart'; | ||||||
import 'package:react/react_client/react_interop.dart'; | ||||||
|
||||||
/// The return value of [useState]. | ||||||
/// | ||||||
/// The current value of the state is available via [value] and | ||||||
/// functions to update it are available via [set] and [setWithUpdater]. | ||||||
/// | ||||||
/// Note there are two rules for using Hooks (<https://reactjs.org/docs/hooks-rules.html>): | ||||||
/// | ||||||
/// * Only call Hooks at the top level. | ||||||
/// * Only call Hooks from inside a [DartFunctionComponent]. | ||||||
/// | ||||||
/// Learn more: <https://reactjs.org/docs/hooks-state.html>. | ||||||
aaronlademann-wf marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
class StateHook<T> { | ||||||
/// The first item of the pair returned by [React.useState]. | ||||||
T _value; | ||||||
|
||||||
/// The second item in the pair returned by [React.useState]. | ||||||
void Function(dynamic) _setValue; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the argument here should be typed as
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing this type breaks the tests for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
|
||||||
StateHook(T initialValue) { | ||||||
final result = React.useState(initialValue); | ||||||
_value = result[0]; | ||||||
_setValue = result[1]; | ||||||
} | ||||||
|
||||||
/// Constructor for [useStateLazy], calls lazy version of [React.useState] to | ||||||
/// initialize [_value] to the return value of [init]. | ||||||
/// | ||||||
/// See: <https://reactjs.org/docs/hooks-reference.html#lazy-initial-state>. | ||||||
StateHook.lazy(T init()) { | ||||||
final result = React.useState(allowInterop(init)); | ||||||
_value = result[0]; | ||||||
_setValue = result[1]; | ||||||
} | ||||||
|
||||||
/// The current value of the state. | ||||||
/// | ||||||
/// See: <https://reactjs.org/docs/hooks-reference.html#usestate>. | ||||||
T get value => _value; | ||||||
|
||||||
/// Updates [value] to [newValue]. | ||||||
/// | ||||||
/// See: <https://reactjs.org/docs/hooks-state.html#updating-state>. | ||||||
void set(T newValue) => _setValue(newValue); | ||||||
|
||||||
/// Updates [value] to the return value of [computeNewValue]. | ||||||
/// | ||||||
/// See: <https://reactjs.org/docs/hooks-reference.html#functional-updates>. | ||||||
void setWithUpdater(T computeNewValue(T oldValue)) => _setValue(allowInterop(computeNewValue)); | ||||||
} | ||||||
|
||||||
/// Adds local state to a [DartFunctionComponent] | ||||||
/// by returning a [StateHook] with [StateHook.value] initialized to [initialValue]. | ||||||
/// | ||||||
/// > __Note:__ If the [initialValue] is expensive to compute, [useStateLazy] should be used instead. | ||||||
/// | ||||||
/// __Example__: | ||||||
/// | ||||||
sydneyjodon-wk marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// ``` | ||||||
/// UseStateTestComponent(Map props) { | ||||||
/// final count = useState(0); | ||||||
/// | ||||||
/// return react.div({}, [ | ||||||
/// count.value, | ||||||
/// react.button({'onClick': (_) => count.set(0)}, ['Reset']), | ||||||
/// react.button({ | ||||||
/// 'onClick': (_) => count.setWithUpdater((prev) => prev + 1), | ||||||
/// }, ['+']), | ||||||
/// ]); | ||||||
/// } | ||||||
/// ``` | ||||||
/// | ||||||
sydneyjodon-wk marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// Learn more: <https://reactjs.org/docs/hooks-state.html>. | ||||||
StateHook<T> useState<T>(T initialValue) => StateHook(initialValue); | ||||||
|
||||||
/// Adds local state to a [DartFunctionComponent] | ||||||
/// by returning a [StateHook] with [StateHook.value] initialized to the return value of [init]. | ||||||
/// | ||||||
/// __Example__: | ||||||
/// | ||||||
sydneyjodon-wk marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// ``` | ||||||
/// UseStateTestComponent(Map props) { | ||||||
/// final count = useStateLazy(() { | ||||||
/// var initialState = someExpensiveComputation(props); | ||||||
/// return initialState; | ||||||
/// })); | ||||||
/// | ||||||
/// return react.div({}, [ | ||||||
/// count.value, | ||||||
/// react.button({'onClick': (_) => count.set(0)}, ['Reset']), | ||||||
/// react.button({'onClick': (_) => count.set((prev) => prev + 1)}, ['+']), | ||||||
/// ]); | ||||||
/// } | ||||||
/// ``` | ||||||
/// | ||||||
sydneyjodon-wk marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// Learn more: <https://reactjs.org/docs/hooks-reference.html#lazy-initial-state>. | ||||||
StateHook<T> useStateLazy<T>(T init()) => StateHook.lazy(init); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// ignore_for_file: deprecated_member_use_from_same_package | ||
@TestOn('browser') | ||
@JS() | ||
library hooks_test; | ||
|
||
import 'dart:html'; | ||
|
||
import "package:js/js.dart"; | ||
import 'package:react/hooks.dart'; | ||
import 'package:react/react.dart' as react; | ||
import 'package:react/react_client.dart'; | ||
import 'package:react/react_dom.dart' as react_dom; | ||
import 'package:react/react_test_utils.dart' as react_test_utils; | ||
import 'package:test/test.dart'; | ||
|
||
main() { | ||
setClientConfiguration(); | ||
|
||
group('React Hooks: ', () { | ||
group('useState -', () { | ||
ReactDartFunctionComponentFactoryProxy UseStateTest; | ||
DivElement textRef; | ||
DivElement countRef; | ||
ButtonElement setButtonRef; | ||
ButtonElement setWithUpdaterButtonRef; | ||
|
||
setUpAll(() { | ||
var mountNode = new DivElement(); | ||
|
||
UseStateTest = react.registerFunctionComponent((Map props) { | ||
final text = useStateLazy(() { | ||
return 'initialValue'; | ||
}); | ||
final count = useState(0); | ||
|
||
return react.div({}, [ | ||
react.div({ | ||
'ref': (ref) { | ||
textRef = ref; | ||
}, | ||
}, [ | ||
text.value | ||
]), | ||
react.div({ | ||
'ref': (ref) { | ||
countRef = ref; | ||
}, | ||
}, [ | ||
count.value | ||
]), | ||
react.button({ | ||
'onClick': (_) => text.set('newValue'), | ||
'ref': (ref) { | ||
setButtonRef = ref; | ||
}, | ||
}, [ | ||
'Set' | ||
]), | ||
react.button({ | ||
'onClick': (_) => count.setWithUpdater((prev) => prev + 1), | ||
'ref': (ref) { | ||
setWithUpdaterButtonRef = ref; | ||
}, | ||
}, [ | ||
'+' | ||
]), | ||
]); | ||
}); | ||
|
||
react_dom.render(UseStateTest({}), mountNode); | ||
}); | ||
|
||
tearDownAll(() { | ||
UseStateTest = null; | ||
}); | ||
|
||
test('initializes state correctly', () { | ||
expect(countRef.text, '0'); | ||
}); | ||
|
||
test('Lazy initializes state correctly', () { | ||
expect(textRef.text, 'initialValue'); | ||
}); | ||
|
||
test('StateHook.set updates state correctly', () { | ||
react_test_utils.Simulate.click(setButtonRef); | ||
expect(textRef.text, 'newValue'); | ||
}); | ||
|
||
test('StateHook.setWithUpdater updates state correctly', () { | ||
react_test_utils.Simulate.click(setWithUpdaterButtonRef); | ||
expect(countRef.text, '1'); | ||
}); | ||
}); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head lang="en"> | ||
<meta charset="UTF-8"> | ||
<title></title> | ||
<script src="packages/react/react.js"></script> | ||
<script src="packages/react/react_dom.js"></script> | ||
<link rel="x-dart-test" href="hooks_test.dart"> | ||
<script src="packages/test/dart.js"></script> | ||
</head> | ||
<body> | ||
</body> | ||
</html> |
Uh oh!
There was an error while loading. Please reload this page.