From 1caaffdcb7d2cb9777618170b3f8a3ead69a9df1 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Wed, 13 Nov 2019 14:08:33 -0700 Subject: [PATCH 01/16] Basic implementation with working example --- example/test/function_component_test.dart | 41 ++++++++++++----------- lib/hooks.dart | 36 ++++++++++++++++++++ lib/react_client/react_interop.dart | 1 + 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index e7b0cb19..ccd24999 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -7,23 +7,24 @@ import 'package:react/react_client.dart'; var useStateTestFunctionComponent = react.registerFunctionComponent(UseStateTestComponent, displayName: 'useStateTest'); +reducer(state, action) { + switch (action['type']) { + case 'increment': + return {'count': state['count'] + 1}; + case 'decrement': + return {'count': state['count'] - 1}; + default: + return new Error(); + } +} + UseStateTestComponent(Map props) { - final count = useState(0); + final state = useReducer(reducer, {'count': 0}); return react.div({}, [ - count.value, - react.button({'onClick': (_) => count.set(0)}, ['Reset']), - react.button({ - 'onClick': (_) => count.setWithUpdater((prev) { - if (props['enabled']) { - return prev + 1; - } else { - return prev; - } - }), - }, [ - '+' - ]), + state.state['count'], + react.button({'onClick': (_) => state.dispatch({'type': 'increment'})}, ['+']), + react.button({'onClick': (_) => state.dispatch({'type': 'decrement'})}, ['-']), ]); } @@ -39,12 +40,12 @@ void main() { 'key': 'useStateTest', 'enabled': true, }, []), - react.br({}), - react.h5({'key': 'useStateTestLabel-2'}, 'Disabled:'), - useStateTestFunctionComponent({ - 'key': 'useStateTest', - 'enabled': false, - }, []), +// react.br({}), +// react.h5({'key': 'useStateTestLabel-2'}, 'Disabled:'), +// useStateTestFunctionComponent({ +// 'key': 'useStateTest', +// 'enabled': false, +// }, []), ]), querySelector('#content')); } diff --git a/lib/hooks.dart b/lib/hooks.dart index b94079b9..b9bf2644 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -107,3 +107,39 @@ StateHook useState(T initialValue) => StateHook(initialValue); /// /// Learn more: . StateHook useStateLazy(T init()) => StateHook.lazy(init); + +class ReducerHook { + /// The first item of the pair returned by [React.userReducer]. + Map _state; + + /// The second item in the pair returned by [React.userReducer]. + void Function(dynamic) _dispatch; + + ReducerHook(Function(Map) reducer, Map initialState) { + final result = React.useReducer(allowInterop(reducer), initialState); + _state = result[0]; + _dispatch = result[1]; + } + + /// Constructor for [useStateLazy], calls lazy version of [React.useState] to + /// initialize [_value] to the return value of [init]. + /// + /// See: . +// ReducerHook.lazy(T init()) { +// final result = React.useState(allowInterop(init)); +// _value = result[0]; +// _setValue = result[1]; +// } + + /// The current value of the state. + /// + /// See: . + Map get state => _state; + + /// Updates [value] to [newValue]. + /// + /// See: . + void dispatch(dynamic action) => _dispatch(action); +} + +ReducerHook useReducer(Function reducer, Map initialState) => ReducerHook(reducer, initialState); diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index de35244a..5b9a1a0a 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -47,6 +47,7 @@ abstract class React { external static ReactClass forwardRef(Function(JsMap props, JsRef ref) wrapperFunction); external static List useState(dynamic value); + external static List useReducer(dynamic Function(Map action) reducer, Map initialState, [Function init]); } /// Creates a [Ref] object that can be attached to a [ReactElement] via the ref prop. From 39e8ebbdd10bf0937f6a84210fe34ab594bdb327 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Wed, 13 Nov 2019 14:47:30 -0700 Subject: [PATCH 02/16] Add lazy useReducer --- example/test/function_component_test.dart | 16 +++++++++--- lib/hooks.dart | 32 +++++++++++------------ lib/react_client/react_interop.dart | 2 +- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index ccd24999..8c3b2eb5 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -7,14 +7,14 @@ import 'package:react/react_client.dart'; var useStateTestFunctionComponent = react.registerFunctionComponent(UseStateTestComponent, displayName: 'useStateTest'); -reducer(state, action) { +Map reducer(Map state, Map action) { switch (action['type']) { case 'increment': return {'count': state['count'] + 1}; case 'decrement': return {'count': state['count'] - 1}; default: - return new Error(); + return state; } } @@ -23,8 +23,16 @@ UseStateTestComponent(Map props) { return react.div({}, [ state.state['count'], - react.button({'onClick': (_) => state.dispatch({'type': 'increment'})}, ['+']), - react.button({'onClick': (_) => state.dispatch({'type': 'decrement'})}, ['-']), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'increment'}) + }, [ + '+' + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'decrement'}) + }, [ + '-' + ]), ]); } diff --git a/lib/hooks.dart b/lib/hooks.dart index b9bf2644..77d71a97 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -108,38 +108,38 @@ StateHook useState(T initialValue) => StateHook(initialValue); /// Learn more: . StateHook useStateLazy(T init()) => StateHook.lazy(init); -class ReducerHook { +class ReducerHook { /// The first item of the pair returned by [React.userReducer]. - Map _state; + S _state; /// The second item in the pair returned by [React.userReducer]. - void Function(dynamic) _dispatch; + void Function(A) _dispatch; - ReducerHook(Function(Map) reducer, Map initialState) { + ReducerHook(S Function(S, A) reducer, S initialState) { final result = React.useReducer(allowInterop(reducer), initialState); _state = result[0]; _dispatch = result[1]; } - /// Constructor for [useStateLazy], calls lazy version of [React.useState] to - /// initialize [_value] to the return value of [init]. - /// - /// See: . -// ReducerHook.lazy(T init()) { -// final result = React.useState(allowInterop(init)); -// _value = result[0]; -// _setValue = result[1]; -// } + ReducerHook.lazy(S Function(S, A) reducer, I initialState, S Function(I) init) { + final result = React.useReducer(allowInterop(reducer), initialState, allowInterop(init)); + _state = result[0]; + _dispatch = result[1]; + } /// The current value of the state. /// /// See: . - Map get state => _state; + S get state => _state; /// Updates [value] to [newValue]. /// /// See: . - void dispatch(dynamic action) => _dispatch(action); + void dispatch(A action) => _dispatch(action); } -ReducerHook useReducer(Function reducer, Map initialState) => ReducerHook(reducer, initialState); +ReducerHook useReducer(S Function(S, A) reducer, S initialState) => + ReducerHook(reducer, initialState); + +ReducerHook useReducerLazy(S Function(S, A) reducer, I initialState, S Function(I) init) => + ReducerHook.lazy(reducer, initialState, init); diff --git a/lib/react_client/react_interop.dart b/lib/react_client/react_interop.dart index 5b9a1a0a..5e783e40 100644 --- a/lib/react_client/react_interop.dart +++ b/lib/react_client/react_interop.dart @@ -47,7 +47,7 @@ abstract class React { external static ReactClass forwardRef(Function(JsMap props, JsRef ref) wrapperFunction); external static List useState(dynamic value); - external static List useReducer(dynamic Function(Map action) reducer, Map initialState, [Function init]); + external static List useReducer(Function reducer, dynamic initialState, [Function init]); } /// Creates a [Ref] object that can be attached to a [ReactElement] via the ref prop. From 06718db200fc6acdbb25a20d8e93c432f7a10d97 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 18 Nov 2019 09:28:59 -0700 Subject: [PATCH 03/16] Add useReducer examples --- example/test/function_component_test.dart | 32 ++++--- lib/hooks.dart | 105 +++++++++++++++++++--- 2 files changed, 115 insertions(+), 22 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index 8c3b2eb5..9d514044 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -5,7 +5,11 @@ import 'package:react/react.dart' as react; import 'package:react/react_dom.dart' as react_dom; import 'package:react/react_client.dart'; -var useStateTestFunctionComponent = react.registerFunctionComponent(UseStateTestComponent, displayName: 'useStateTest'); +var useReducerTestFunctionComponent = react.registerFunctionComponent(UseReducerTestComponent, displayName: 'useReducerTest'); + +Map initializeCount(int initialValue) { + return {'count': initialValue}; +} Map reducer(Map state, Map action) { switch (action['type']) { @@ -13,15 +17,17 @@ Map reducer(Map state, Map action) { return {'count': state['count'] + 1}; case 'decrement': return {'count': state['count'] - 1}; + case 'reset': + return initializeCount(action['payload']); default: return state; } } -UseStateTestComponent(Map props) { - final state = useReducer(reducer, {'count': 0}); +UseReducerTestComponent(Map props) { + final state = useReducerLazy(reducer, props['initialCount'], initializeCount); - return react.div({}, [ + return react.Fragment({}, [ state.state['count'], react.button({ 'onClick': (_) => state.dispatch({'type': 'increment'}) @@ -33,6 +39,14 @@ UseStateTestComponent(Map props) { }, [ '-' ]), + react.button({ + 'onClick': (_) => state.dispatch({ + 'type': 'reset', + 'payload': props['initialCount'], + }) + }, [ + 'reset' + ]), ]); } @@ -44,16 +58,10 @@ void main() { react.Fragment({}, [ react.h1({'key': 'functionComponentTestLabel'}, ['Function Component Tests']), react.h2({'key': 'useStateTestLabel'}, ['useState Hook Test']), - useStateTestFunctionComponent({ + useReducerTestFunctionComponent({ 'key': 'useStateTest', - 'enabled': true, + 'initialCount': 10, }, []), -// react.br({}), -// react.h5({'key': 'useStateTestLabel-2'}, 'Disabled:'), -// useStateTestFunctionComponent({ -// 'key': 'useStateTest', -// 'enabled': false, -// }, []), ]), querySelector('#content')); } diff --git a/lib/hooks.dart b/lib/hooks.dart index 77d71a97..959eda1d 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -108,20 +108,20 @@ StateHook useState(T initialValue) => StateHook(initialValue); /// Learn more: . StateHook useStateLazy(T init()) => StateHook.lazy(init); -class ReducerHook { +class ReducerHook { /// The first item of the pair returned by [React.userReducer]. - S _state; + Map _state; /// The second item in the pair returned by [React.userReducer]. - void Function(A) _dispatch; + void Function(dynamic) _dispatch; - ReducerHook(S Function(S, A) reducer, S initialState) { + ReducerHook(Function reducer, dynamic initialState) { final result = React.useReducer(allowInterop(reducer), initialState); _state = result[0]; _dispatch = result[1]; } - ReducerHook.lazy(S Function(S, A) reducer, I initialState, S Function(I) init) { + ReducerHook.lazy(Function reducer, dynamic initialState, Function init) { final result = React.useReducer(allowInterop(reducer), initialState, allowInterop(init)); _state = result[0]; _dispatch = result[1]; @@ -130,16 +130,101 @@ class ReducerHook { /// The current value of the state. /// /// See: . - S get state => _state; + Map get state => _state; /// Updates [value] to [newValue]. /// /// See: . - void dispatch(A action) => _dispatch(action); + void dispatch(dynamic action) => _dispatch(action); } -ReducerHook useReducer(S Function(S, A) reducer, S initialState) => - ReducerHook(reducer, initialState); +/// +/// +/// __Example__: +/// +/// ``` +/// Map reducer(Map state, Map action) { +/// switch (action['type']) { +/// case 'increment': +/// return {'count': state['count'] + 1}; +/// case 'decrement': +/// return {'count': state['count'] - 1}; +/// default: +/// return state; +/// } +/// } +/// +/// UseReducerTestComponent(Map props) { +/// final state = useReducer(reducer, {'count': 0}); +/// +/// return react.Fragment({}, [ +/// state.state['count'], +/// react.button({ +/// 'onClick': (_) => state.dispatch({'type': 'increment'}) +/// }, [ +/// '+' +/// ]), +/// react.button({ +/// 'onClick': (_) => state.dispatch({'type': 'decrement'}) +/// }, [ +/// '-' +/// ]), +/// ]); +/// } +/// ``` +/// +/// See: . +ReducerHook useReducer(Function reducer, dynamic initialState) => ReducerHook(reducer, initialState); -ReducerHook useReducerLazy(S Function(S, A) reducer, I initialState, S Function(I) init) => +/// +/// +/// __Example__: +/// +/// ``` +/// Map initializeCount(int initialValue) { +/// return {'count': initialValue}; +/// } +/// +/// Map reducer(Map state, Map action) { +/// switch (action['type']) { +/// case 'increment': +/// return {'count': state['count'] + 1}; +/// case 'decrement': +/// return {'count': state['count'] - 1}; +/// case 'reset': +/// return initializeCount(action['payload']); +/// default: +/// return state; +/// } +/// } +/// +/// UseReducerTestComponent(Map props) { +/// final state = useReducerLazy(reducer, props['initialCount'], initializeCount); +/// +/// return react.Fragment({}, [ +/// state.state['count'], +/// react.button({ +/// 'onClick': (_) => state.dispatch({'type': 'increment'}) +/// }, [ +/// '+' +/// ]), +/// react.button({ +/// 'onClick': (_) => state.dispatch({'type': 'decrement'}) +/// }, [ +/// '-' +/// ]), +/// react.button({ +/// 'onClick': (_) => state.dispatch({ +/// 'type': 'reset', +/// 'payload': props['initialCount'], +/// }) +/// }, [ +/// 'reset' +/// ]), +/// ]); +/// } +/// ``` +/// +/// See: . +ReducerHook useReducerLazy(Function reducer, dynamic initialState, Function init) => ReducerHook.lazy(reducer, initialState, init); From e79724af5d0b55b216024be354649fffeaa9f45a Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 18 Nov 2019 09:34:08 -0700 Subject: [PATCH 04/16] Update examples --- example/test/function_component_test.dart | 45 ++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index 9d514044..244fe878 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -5,7 +5,30 @@ import 'package:react/react.dart' as react; import 'package:react/react_dom.dart' as react_dom; import 'package:react/react_client.dart'; -var useReducerTestFunctionComponent = react.registerFunctionComponent(UseReducerTestComponent, displayName: 'useReducerTest'); +var useStateTestFunctionComponent = react.registerFunctionComponent(UseStateTestComponent, displayName: 'useStateTest'); + +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) { + if (props['enabled']) { + return prev + 1; + } else { + return prev; + } + }), + }, [ + '+' + ]), + ]); +} + +var useReducerTestFunctionComponent = + react.registerFunctionComponent(UseReducerTestComponent, displayName: 'useReducerTest'); Map initializeCount(int initialValue) { return {'count': initialValue}; @@ -41,9 +64,9 @@ UseReducerTestComponent(Map props) { ]), react.button({ 'onClick': (_) => state.dispatch({ - 'type': 'reset', - 'payload': props['initialCount'], - }) + 'type': 'reset', + 'payload': props['initialCount'], + }) }, [ 'reset' ]), @@ -58,8 +81,20 @@ void main() { react.Fragment({}, [ react.h1({'key': 'functionComponentTestLabel'}, ['Function Component Tests']), react.h2({'key': 'useStateTestLabel'}, ['useState Hook Test']), - useReducerTestFunctionComponent({ + useStateTestFunctionComponent({ 'key': 'useStateTest', + 'enabled': true, + }, []), + react.br({}), + react.h5({'key': 'useStateTestLabel-2'}, 'Disabled:'), + useStateTestFunctionComponent({ + 'key': 'useStateTest2', + 'enabled': false, + }, []), + react.br({}), + react.h2({'key': 'useReducerTestLabel'}, ['useReducer Hook Test']), + useReducerTestFunctionComponent({ + 'key': 'useReducerTest', 'initialCount': 10, }, []), ]), From 631812bdb1152b50259033be7be73bd4932ee8aa Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 18 Nov 2019 10:06:20 -0700 Subject: [PATCH 05/16] Add doc comments --- example/test/function_component_test.dart | 12 +++--- lib/hooks.dart | 49 +++++++++++++++-------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index 244fe878..bff708fb 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -15,12 +15,12 @@ UseStateTestComponent(Map props) { react.button({'onClick': (_) => count.set(0)}, ['Reset']), react.button({ 'onClick': (_) => count.setWithUpdater((prev) { - if (props['enabled']) { - return prev + 1; - } else { - return prev; - } - }), + if (props['enabled']) { + return prev + 1; + } else { + return prev; + } + }), }, [ '+' ]), diff --git a/lib/hooks.dart b/lib/hooks.dart index 959eda1d..4742e7e0 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -108,37 +108,53 @@ StateHook useState(T initialValue) => StateHook(initialValue); /// Learn more: . StateHook useStateLazy(T init()) => StateHook.lazy(init); +/// The return value of [useReducer]. +/// +/// The current state is available via [state] and action dispatcher is available via [dispatch]. +/// +/// Note there are two rules for using Hooks (): +/// +/// * Only call Hooks at the top level. +/// * Only call Hooks from inside a [DartFunctionComponent]. +/// +/// Learn more: . class ReducerHook { /// The first item of the pair returned by [React.userReducer]. Map _state; /// The second item in the pair returned by [React.userReducer]. - void Function(dynamic) _dispatch; + void Function(Map) _dispatch; - ReducerHook(Function reducer, dynamic initialState) { + ReducerHook(Map Function(Map state, Map action) reducer, Map initialState) { final result = React.useReducer(allowInterop(reducer), initialState); _state = result[0]; _dispatch = result[1]; } - ReducerHook.lazy(Function reducer, dynamic initialState, Function init) { - final result = React.useReducer(allowInterop(reducer), initialState, allowInterop(init)); + /// Constructor for [useReducerLazy], calls lazy version of [React.useReducer] to + /// initialize [_state] to the return value of [init(initialArg)]. + /// + /// See: . + ReducerHook.lazy(Map Function(Map state, Map action) reducer, dynamic initialArg, Function init) { + final result = React.useReducer(allowInterop(reducer), initialArg, allowInterop(init)); _state = result[0]; _dispatch = result[1]; } - /// The current value of the state. + /// The current state map of the component. /// - /// See: . + /// See: . Map get state => _state; - /// Updates [value] to [newValue]. + /// Dispatches [action] and triggers stage changes. /// - /// See: . - void dispatch(dynamic action) => _dispatch(action); + /// > __Note:__ The dispatch function identity is stable and will not change on re-renders. + /// + /// See: . + void dispatch(Map action) => _dispatch(action); } -/// +/// Initializes state of a [DartFunctionComponent] to [initialState] and creates [dispatch] method. /// /// __Example__: /// @@ -173,10 +189,11 @@ class ReducerHook { /// } /// ``` /// -/// See: . -ReducerHook useReducer(Function reducer, dynamic initialState) => ReducerHook(reducer, initialState); +/// Learn more: . +ReducerHook useReducer(Map Function(Map state, Map action) reducer, Map initialState) => + ReducerHook(reducer, initialState); -/// +/// Initializes state of a [DartFunctionComponent] to [init(initialArg)] and creates [dispatch] method. /// /// __Example__: /// @@ -225,6 +242,6 @@ ReducerHook useReducer(Function reducer, dynamic initialState) => ReducerHook(re /// } /// ``` /// -/// See: . -ReducerHook useReducerLazy(Function reducer, dynamic initialState, Function init) => - ReducerHook.lazy(reducer, initialState, init); +/// Learn more: . +ReducerHook useReducerLazy(Map Function(Map state, Map action) reducer, dynamic initialArg, Function init) => + ReducerHook.lazy(reducer, initialArg, init); From ee69da87af55e6dcda4eca36a3a788a9702e30b4 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 18 Nov 2019 10:45:02 -0700 Subject: [PATCH 06/16] Add tests --- test/hooks_test.dart | 97 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/test/hooks_test.dart b/test/hooks_test.dart index e80492df..ff26c657 100644 --- a/test/hooks_test.dart +++ b/test/hooks_test.dart @@ -92,5 +92,102 @@ main() { expect(countRef.text, '1'); }); }); + + group('useReducer -', () { + ReactDartFunctionComponentFactoryProxy UseReducerTest; + DivElement textRef; + DivElement countRef; + ButtonElement addButtonRef; + ButtonElement subtractButtonRef; + ButtonElement textButtonRef; + + Map reducer(Map state, Map action) { + switch (action['type']) { + case 'increment': + return {'count': state['count'] + 1}; + case 'decrement': + return {'count': state['count'] - 1}; + case 'changeText': + return {'text': action['newText']}; + default: + return state; + } + } + + setUpAll(() { + var mountNode = new DivElement(); + + UseReducerTest = react.registerFunctionComponent((Map props) { + final state = useReducer(reducer, {'text': 'initialValue', 'count': 0,}); + + return react.div({}, [ + react.div({ + 'ref': (ref) { + textRef = ref; + }, + }, [ + state.state['text'] + ]), + react.div({ + 'ref': (ref) { + countRef = ref; + }, + }, [ + state.state['count'] + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'changeText', 'newText': 'newValue'}), + 'ref': (ref) { + textButtonRef = ref; + }, + }, [ + 'Set' + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'increment'}), + 'ref': (ref) { + addButtonRef = ref; + }, + }, [ + '+' + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'decrement'}), + 'ref': (ref) { + subtractButtonRef = ref; + }, + }, [ + '-' + ]), + ]); + }); + + react_dom.render(UseReducerTest({}), mountNode); + }); + + tearDownAll(() { + UseReducerTest = null; + }); + + test('initializes state correctly', () { + expect(countRef.text, '0'); + expect(textRef.text, 'initialValue'); + }); + + test('StateHook.set updates state correctly', () { + react_test_utils.Simulate.click(textButtonRef); + expect(textRef.text, 'newValue'); + }); + + test('StateHook.setWithUpdater updates state correctly', () { + react_test_utils.Simulate.click(addButtonRef); + expect(countRef.text, 'a'); + }); + + test('StateHook.setWithUpdater updates state correctly', () { + react_test_utils.Simulate.click(subtractButtonRef); + expect(countRef.text, 'a'); + }); + }); }); } From 4af95ff9f00a20623d3f0786a27abb36d160f9c9 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 18 Nov 2019 11:08:47 -0700 Subject: [PATCH 07/16] Update tests --- example/test/function_component_test.dart | 4 +- lib/hooks.dart | 8 +- test/hooks_test.dart | 105 +++++++++++++++++++--- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index bff708fb..bc15554a 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -37,9 +37,9 @@ Map initializeCount(int initialValue) { Map reducer(Map state, Map action) { switch (action['type']) { case 'increment': - return {'count': state['count'] + 1}; + return {...state, 'count': state['count'] + 1}; case 'decrement': - return {'count': state['count'] - 1}; + return {...state, 'count': state['count'] - 1}; case 'reset': return initializeCount(action['payload']); default: diff --git a/lib/hooks.dart b/lib/hooks.dart index 4742e7e0..08f18ce3 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -162,9 +162,9 @@ class ReducerHook { /// Map reducer(Map state, Map action) { /// switch (action['type']) { /// case 'increment': -/// return {'count': state['count'] + 1}; +/// return {...state, 'count': state['count'] + 1}; /// case 'decrement': -/// return {'count': state['count'] - 1}; +/// return {...state, 'count': state['count'] - 1}; /// default: /// return state; /// } @@ -205,9 +205,9 @@ ReducerHook useReducer(Map Function(Map state, Map action) reducer, Map initialS /// Map reducer(Map state, Map action) { /// switch (action['type']) { /// case 'increment': -/// return {'count': state['count'] + 1}; +/// return {...state, 'count': state['count'] + 1}; /// case 'decrement': -/// return {'count': state['count'] - 1}; +/// return {...state, 'count': state['count'] - 1}; /// case 'reset': /// return initializeCount(action['payload']); /// default: diff --git a/test/hooks_test.dart b/test/hooks_test.dart index ff26c657..10a1e721 100644 --- a/test/hooks_test.dart +++ b/test/hooks_test.dart @@ -104,11 +104,11 @@ main() { Map reducer(Map state, Map action) { switch (action['type']) { case 'increment': - return {'count': state['count'] + 1}; + return {...state, 'count': state['count'] + 1}; case 'decrement': - return {'count': state['count'] - 1}; + return {...state, 'count': state['count'] - 1}; case 'changeText': - return {'text': action['newText']}; + return {...state, 'text': action['newText']}; default: return state; } @@ -118,7 +118,10 @@ main() { var mountNode = new DivElement(); UseReducerTest = react.registerFunctionComponent((Map props) { - final state = useReducer(reducer, {'text': 'initialValue', 'count': 0,}); + final state = useReducer(reducer, { + 'text': 'initialValue', + 'count': 0, + }); return react.div({}, [ react.div({ @@ -174,19 +177,99 @@ main() { expect(textRef.text, 'initialValue'); }); - test('StateHook.set updates state correctly', () { + test('dispatch updates states correctly', () { react_test_utils.Simulate.click(textButtonRef); expect(textRef.text, 'newValue'); - }); - test('StateHook.setWithUpdater updates state correctly', () { react_test_utils.Simulate.click(addButtonRef); - expect(countRef.text, 'a'); - }); + expect(countRef.text, '1'); - test('StateHook.setWithUpdater updates state correctly', () { react_test_utils.Simulate.click(subtractButtonRef); - expect(countRef.text, 'a'); + expect(countRef.text, '0'); + }); + + group('useReducerLazy', () { + ButtonElement resetButtonRef; + + Map initializeCount(int initialValue) { + return {'count': initialValue}; + } + + Map reducer(Map state, Map action) { + switch (action['type']) { + case 'increment': + return {...state, 'count': state['count'] + 1}; + case 'decrement': + return {...state, 'count': state['count'] - 1}; + case 'reset': + return initializeCount(action['payload']); + default: + return state; + } + } + + setUpAll(() { + var mountNode = new DivElement(); + + UseReducerTest = react.registerFunctionComponent((Map props) { + final state = useReducerLazy(reducer, props['initialCount'], initializeCount); + + return react.div({}, [ + react.div({ + 'ref': (ref) { + countRef = ref; + }, + }, [ + state.state['count'] + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'reset', 'payload': props['initialCount']}), + 'ref': (ref) { + resetButtonRef = ref; + }, + }, [ + 'reset' + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'increment'}), + 'ref': (ref) { + addButtonRef = ref; + }, + }, [ + '+' + ]), + react.button({ + 'onClick': (_) => state.dispatch({'type': 'decrement'}), + 'ref': (ref) { + subtractButtonRef = ref; + }, + }, [ + '-' + ]), + ]); + }); + + react_dom.render(UseReducerTest({'initialCount': 10}), mountNode); + }); + + tearDownAll(() { + UseReducerTest = null; + }); + + test('initializes state correctly', () { + expect(countRef.text, '10'); + }); + + test('dispatch updates states correctly', () { + react_test_utils.Simulate.click(addButtonRef); + expect(countRef.text, '11'); + + react_test_utils.Simulate.click(resetButtonRef); + expect(countRef.text, '10'); + + react_test_utils.Simulate.click(subtractButtonRef); + expect(countRef.text, '9'); + }); }); }); }); From 5a75b37dbfc3ba529770fbda7dd283762cc8812d Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 18 Nov 2019 11:15:32 -0700 Subject: [PATCH 08/16] Update tests --- test/hooks_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hooks_test.dart b/test/hooks_test.dart index 10a1e721..453053d7 100644 --- a/test/hooks_test.dart +++ b/test/hooks_test.dart @@ -101,7 +101,7 @@ main() { ButtonElement subtractButtonRef; ButtonElement textButtonRef; - Map reducer(Map state, Map action) { + Map reducer2(Map state, Map action) { switch (action['type']) { case 'increment': return {...state, 'count': state['count'] + 1}; @@ -118,7 +118,7 @@ main() { var mountNode = new DivElement(); UseReducerTest = react.registerFunctionComponent((Map props) { - final state = useReducer(reducer, { + final state = useReducer(reducer2, { 'text': 'initialValue', 'count': 0, }); From 7c0f69499b3494ab0f0992190cdb161cf00e40ed Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 18 Nov 2019 11:16:12 -0700 Subject: [PATCH 09/16] Update tests --- test/hooks_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/hooks_test.dart b/test/hooks_test.dart index 453053d7..e89ad688 100644 --- a/test/hooks_test.dart +++ b/test/hooks_test.dart @@ -101,7 +101,7 @@ main() { ButtonElement subtractButtonRef; ButtonElement textButtonRef; - Map reducer2(Map state, Map action) { + Map reducer(Map state, Map action) { switch (action['type']) { case 'increment': return {...state, 'count': state['count'] + 1}; @@ -118,7 +118,7 @@ main() { var mountNode = new DivElement(); UseReducerTest = react.registerFunctionComponent((Map props) { - final state = useReducer(reducer2, { + final state = useReducer(reducer, { 'text': 'initialValue', 'count': 0, }); @@ -195,7 +195,7 @@ main() { return {'count': initialValue}; } - Map reducer(Map state, Map action) { + Map reducer2(Map state, Map action) { switch (action['type']) { case 'increment': return {...state, 'count': state['count'] + 1}; @@ -212,7 +212,7 @@ main() { var mountNode = new DivElement(); UseReducerTest = react.registerFunctionComponent((Map props) { - final state = useReducerLazy(reducer, props['initialCount'], initializeCount); + final state = useReducerLazy(reducer2, props['initialCount'], initializeCount); return react.div({}, [ react.div({ From 37e7c5a8e976fdedc2501b4f67685a6f3fb945e6 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 25 Nov 2019 09:11:09 -0700 Subject: [PATCH 10/16] Update doc comment note format --- lib/hooks.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/hooks.dart b/lib/hooks.dart index 99019e5b..ae2b8b09 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -10,10 +10,10 @@ import 'package:react/react_client/react_interop.dart'; /// 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 (): -/// -/// * Only call Hooks at the top level. -/// * Only call Hooks from inside a [DartFunctionComponent]. +/// > __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: . class StateHook { @@ -106,10 +106,10 @@ StateHook useStateLazy(T init()) => StateHook.lazy(init); /// /// The current state is available via [state] and action dispatcher is available via [dispatch]. /// -/// Note there are two rules for using Hooks (): -/// -/// * Only call Hooks at the top level. -/// * Only call Hooks from inside a [DartFunctionComponent]. +/// > __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: . class ReducerHook { From 243c5d6f6f478747d2402cfc4e6dc420d9fc8bb2 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 2 Dec 2019 14:46:27 -0700 Subject: [PATCH 11/16] Update typing --- example/test/function_component_test.dart | 2 +- lib/hooks.dart | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index edad7636..ca16471d 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -42,7 +42,7 @@ Map reducer(Map state, Map action) { } UseReducerTestComponent(Map props) { - final state = useReducerLazy(reducer, props['initialCount'], initializeCount); + final ReducerHook state = useReducerLazy(reducer, props['initialCount'], initializeCount); return react.Fragment({}, [ state.state['count'], diff --git a/lib/hooks.dart b/lib/hooks.dart index b5ad5d18..cb933762 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -112,14 +112,14 @@ StateHook useStateLazy(T init()) => StateHook.lazy(init); /// > * Only call Hooks from inside a [DartFunctionComponent]. /// /// Learn more: . -class ReducerHook { +class ReducerHook { /// The first item of the pair returned by [React.userReducer]. - Map _state; + TState _state; /// The second item in the pair returned by [React.userReducer]. - void Function(Map) _dispatch; + void Function(TActions) _dispatch; - ReducerHook(Map Function(Map state, Map action) reducer, Map initialState) { + ReducerHook(TState Function(TState state, TActions action) reducer, TState initialState) { final result = React.useReducer(allowInterop(reducer), initialState); _state = result[0]; _dispatch = result[1]; @@ -129,7 +129,7 @@ class ReducerHook { /// initialize [_state] to the return value of [init(initialArg)]. /// /// See: . - ReducerHook.lazy(Map Function(Map state, Map action) reducer, dynamic initialArg, Function init) { + ReducerHook.lazy(TState Function(TState state, TActions action) reducer, TInit initialArg, TState Function(TInit) init) { final result = React.useReducer(allowInterop(reducer), initialArg, allowInterop(init)); _state = result[0]; _dispatch = result[1]; @@ -138,14 +138,14 @@ class ReducerHook { /// The current state map of the component. /// /// See: . - Map get state => _state; + TState get state => _state; /// Dispatches [action] and triggers stage changes. /// /// > __Note:__ The dispatch function identity is stable and will not change on re-renders. /// /// See: . - void dispatch(Map action) => _dispatch(action); + void dispatch(TActions action) => _dispatch(action); } /// Initializes state of a [DartFunctionComponent] to [initialState] and creates [dispatch] method. @@ -184,7 +184,7 @@ class ReducerHook { /// ``` /// /// Learn more: . -ReducerHook useReducer(Map Function(Map state, Map action) reducer, Map initialState) => +ReducerHook useReducer(TState Function(TState state, TActions action) reducer, TState initialState) => ReducerHook(reducer, initialState); /// Initializes state of a [DartFunctionComponent] to [init(initialArg)] and creates [dispatch] method. @@ -237,7 +237,7 @@ ReducerHook useReducer(Map Function(Map state, Map action) reducer, Map initialS /// ``` /// /// Learn more: . -ReducerHook useReducerLazy(Map Function(Map state, Map action) reducer, dynamic initialArg, Function init) => +ReducerHook useReducerLazy(TState Function(TState state, TActions action) reducer, TInit initialArg, TState Function(TInit) init) => ReducerHook.lazy(reducer, initialArg, init); /// Returns a memoized version of [callback] that only changes if one of the [dependencies] has changed. From 39f32c087ebd979c8bd1bcf788ead372154c1ca0 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 2 Dec 2019 14:49:48 -0700 Subject: [PATCH 12/16] Update doc comment --- lib/hooks.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hooks.dart b/lib/hooks.dart index cb933762..0a901f0b 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -210,7 +210,7 @@ ReducerHook useReducer(TState /// } /// /// UseReducerTestComponent(Map props) { -/// final state = useReducerLazy(reducer, props['initialCount'], initializeCount); +/// final ReducerHook state = useReducerLazy(reducer, props['initialCount'], initializeCount); /// /// return react.Fragment({}, [ /// state.state['count'], From 2648bfd5362dc55503c4cdf9adfa442319cbe785 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 2 Dec 2019 14:50:30 -0700 Subject: [PATCH 13/16] Format --- example/test/function_component_test.dart | 2 +- lib/hooks.dart | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index ca16471d..22b0f714 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -68,7 +68,7 @@ UseReducerTestComponent(Map props) { } var useCallbackTestFunctionComponent = -react.registerFunctionComponent(UseCallbackTestComponent, displayName: 'useCallbackTest'); + react.registerFunctionComponent(UseCallbackTestComponent, displayName: 'useCallbackTest'); UseCallbackTestComponent(Map props) { final count = useState(0); diff --git a/lib/hooks.dart b/lib/hooks.dart index 0a901f0b..35d6e346 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -129,7 +129,8 @@ class ReducerHook { /// initialize [_state] to the return value of [init(initialArg)]. /// /// See: . - ReducerHook.lazy(TState Function(TState state, TActions action) reducer, TInit initialArg, TState Function(TInit) init) { + ReducerHook.lazy( + TState Function(TState state, TActions action) reducer, TInit initialArg, TState Function(TInit) init) { final result = React.useReducer(allowInterop(reducer), initialArg, allowInterop(init)); _state = result[0]; _dispatch = result[1]; @@ -184,7 +185,8 @@ class ReducerHook { /// ``` /// /// Learn more: . -ReducerHook useReducer(TState Function(TState state, TActions action) reducer, TState initialState) => +ReducerHook useReducer( + TState Function(TState state, TActions action) reducer, TState initialState) => ReducerHook(reducer, initialState); /// Initializes state of a [DartFunctionComponent] to [init(initialArg)] and creates [dispatch] method. @@ -237,7 +239,8 @@ ReducerHook useReducer(TState /// ``` /// /// Learn more: . -ReducerHook useReducerLazy(TState Function(TState state, TActions action) reducer, TInit initialArg, TState Function(TInit) init) => +ReducerHook useReducerLazy( + TState Function(TState state, TActions action) reducer, TInit initialArg, TState Function(TInit) init) => ReducerHook.lazy(reducer, initialArg, init); /// Returns a memoized version of [callback] that only changes if one of the [dependencies] has changed. From 0f6c7d627c1d6df196e47a0756698b5701de29c3 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 2 Dec 2019 14:58:00 -0700 Subject: [PATCH 14/16] Fix test --- test/hooks_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hooks_test.dart b/test/hooks_test.dart index 7c1db28e..6117fd05 100644 --- a/test/hooks_test.dart +++ b/test/hooks_test.dart @@ -212,7 +212,7 @@ main() { var mountNode = new DivElement(); UseReducerTest = react.registerFunctionComponent((Map props) { - final state = useReducerLazy(reducer2, props['initialCount'], initializeCount); + final ReducerHook state = useReducerLazy(reducer2, props['initialCount'], initializeCount); return react.div({}, [ react.div({ From 0da766760932579a8352a8f89463155059118cc6 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 6 Jan 2020 10:39:33 -0700 Subject: [PATCH 15/16] Update merge --- example/test/function_component_test.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/example/test/function_component_test.dart b/example/test/function_component_test.dart index 3b3a9e0c..dd6ebc98 100644 --- a/example/test/function_component_test.dart +++ b/example/test/function_component_test.dart @@ -43,16 +43,19 @@ UseReducerTestComponent(Map props) { return react.Fragment({}, [ state.state['count'], react.button({ + 'key': 'urt1', 'onClick': (_) => state.dispatch({'type': 'increment'}) }, [ '+' ]), react.button({ + 'key': 'urt2', 'onClick': (_) => state.dispatch({'type': 'decrement'}) }, [ '-' ]), react.button({ + 'key': 'urt3', 'onClick': (_) => state.dispatch({ 'type': 'reset', 'payload': props['initialCount'], @@ -162,19 +165,21 @@ void main() { useCallbackTestFunctionComponent({ 'key': 'useCallbackTest', }, []), + react.br({'key': 'br2'}), + react.h2({'key': 'useContextTestLabel'}, ['useContext Hook Test']), newContextProviderComponent({ 'key': 'provider' }, [ useContextTestFunctionComponent({ 'key': 'useContextTest', }, []), - react.br({'key': 'br2'}), - react.h2({'key': 'useReducerTestLabel'}, ['useReducer Hook Test']), - useReducerTestFunctionComponent({ - 'key': 'useReducerTest', - 'initialCount': 10, - }, []), ]), + react.br({'key': 'br3'}), + react.h2({'key': 'useReducerTestLabel'}, ['useReducer Hook Test']), + useReducerTestFunctionComponent({ + 'key': 'useReducerTest', + 'initialCount': 10, + }, []), ]), querySelector('#content')); } From a322c6180c31443fe03854794b943675c96c65b6 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 14 Jan 2020 13:44:48 -0700 Subject: [PATCH 16/16] Change TActions to TAction --- lib/hooks.dart | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/hooks.dart b/lib/hooks.dart index 15f30e6f..0003bc62 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -168,14 +168,14 @@ void useEffect(dynamic Function() sideEffect, [List dependencies]) { /// > * Only call Hooks from inside a [DartFunctionComponent]. /// /// Learn more: . -class ReducerHook { +class ReducerHook { /// The first item of the pair returned by [React.userReducer]. TState _state; /// The second item in the pair returned by [React.userReducer]. - void Function(TActions) _dispatch; + void Function(TAction) _dispatch; - ReducerHook(TState Function(TState state, TActions action) reducer, TState initialState) { + ReducerHook(TState Function(TState state, TAction action) reducer, TState initialState) { final result = React.useReducer(allowInterop(reducer), initialState); _state = result[0]; _dispatch = result[1]; @@ -186,7 +186,7 @@ class ReducerHook { /// /// See: . ReducerHook.lazy( - TState Function(TState state, TActions action) reducer, TInit initialArg, TState Function(TInit) init) { + TState Function(TState state, TAction action) reducer, TInit initialArg, TState Function(TInit) init) { final result = React.useReducer(allowInterop(reducer), initialArg, allowInterop(init)); _state = result[0]; _dispatch = result[1]; @@ -202,7 +202,7 @@ class ReducerHook { /// > __Note:__ The dispatch function identity is stable and will not change on re-renders. /// /// See: . - void dispatch(TActions action) => _dispatch(action); + void dispatch(TAction action) => _dispatch(action); } /// Initializes state of a [DartFunctionComponent] to [initialState] and creates [dispatch] method. @@ -241,8 +241,8 @@ class ReducerHook { /// ``` /// /// Learn more: . -ReducerHook useReducer( - TState Function(TState state, TActions action) reducer, TState initialState) => +ReducerHook useReducer( + TState Function(TState state, TAction action) reducer, TState initialState) => ReducerHook(reducer, initialState); /// Initializes state of a [DartFunctionComponent] to [init(initialArg)] and creates [dispatch] method. @@ -295,8 +295,8 @@ ReducerHook useReducer( /// ``` /// /// Learn more: . -ReducerHook useReducerLazy( - TState Function(TState state, TActions action) reducer, TInit initialArg, TState Function(TInit) init) => +ReducerHook useReducerLazy( + TState Function(TState state, TAction action) reducer, TInit initialArg, TState Function(TInit) init) => ReducerHook.lazy(reducer, initialArg, init); /// Returns a memoized version of [callback] that only changes if one of the [dependencies] has changed.