11import { useMemo , useReducer } from "react" ;
22
3- class HookActions {
4- constructor ( contract , dispatch ) {
5- this . __dispatch = dispatch ;
6-
7- if ( typeof contract === "function" ) {
8- const _contract = new contract ( ) ;
9- contract = { } ;
10- Object . getOwnPropertyNames ( _contract . __proto__ )
11- . splice ( 1 ) // remove the constructor
12- . forEach ( key => ( contract [ key ] = _contract [ key ] ) ) ;
13- }
14-
15- for ( let key in contract ) {
16- this [ key ] = async ( ...args ) => {
17- const newState = await contract [ key ] . apply ( { state : this . __state } , [
18- ...args ,
19- this . __state
20- ] ) ;
21- this . __dispatch ( {
22- newState : newState
23- } ) ;
24- } ;
25- }
3+ /**
4+ * Helper function to take a contract and dispatch callback to transfrom
5+ * them into an object of actions which ultimately dispatch to an underlying
6+ * reducer.
7+ *
8+ * Example:
9+ * const contract = {
10+ * foo(bar, state) {
11+ * return {
12+ * ...state,
13+ * bar
14+ * };
15+ * }
16+ * };
17+ *
18+ * This contract will be turned into an action that is analogous to:
19+ * {
20+ * foo(bar) {
21+ * dispatch({ "reduce": state => contract.foo(bar, state) });
22+ * }
23+ * }
24+ *
25+ *
26+ * Async contract functions are a little different since they return a promise.
27+ *
28+ * Example:
29+ * const contract = {
30+ * async foo(bar, state) {
31+ * return state => ({
32+ * ...state,
33+ * bar
34+ * });
35+ * }
36+ * };
37+ *
38+ * This contract will be turned into an action that is analgous to:
39+ * {
40+ * foo(bar) {
41+ * dispatch({ "reduce": state => state });
42+ * contract.foo(bar, state).then(reducer => dispatch({
43+ * "reduce": state => reducer(state)
44+ * }));
45+ * }
46+ * }
47+ *
48+ * @param contract The contract from which actions are created
49+ * @param dispatch The underlying useReducer's dispatch callback
50+ */
51+ function createActions ( contract , dispatch ) {
52+ const hookActions = { } ;
53+
54+ for ( let key in contract ) {
55+ hookActions [ key ] = ( ...args ) => {
56+ dispatch ( {
57+ reduce : state => {
58+ const newState = contract [ key ] ( ...args , state ) ;
59+
60+ if ( typeof newState . then === "undefined" ) {
61+ // This was a non-async func; just return the new state
62+ return newState ;
63+ }
64+
65+ newState . then ( reducer => {
66+ let error ;
67+ if ( typeof reducer !== "function" ) {
68+ error = new TypeError (
69+ `async action "${ key } " must return a reducer function; instead got "${ typeof reducer } "`
70+ ) ;
71+ }
72+ // Once the promise is resolved, we need to dispatch a new
73+ // action based on the reducer function the async func
74+ // returns given the new state at the time of the resolution.
75+ dispatch ( {
76+ reduce : state => reducer ( state ) ,
77+ error
78+ } ) ;
79+ } ) ;
80+
81+ // Async func cannot mutate state directly; return current state.
82+ return state ;
83+ }
84+ } ) ;
85+ } ;
2686 }
87+
88+ return hookActions ;
89+ }
90+
91+ // We do not inline this because it would cause 2 renders on first use.
92+ function reducer ( state , action ) {
93+ if ( action . error ) {
94+ throw action . error ;
95+ }
96+ return action . reduce ( state ) ;
2797}
2898
2999/**
@@ -34,7 +104,7 @@ class HookActions {
34104 * @returns [state, actions] - the current state of the governor and the
35105 * actions that can be invoked.
36106 */
37- export function useGovernor ( initialState = { } , contract = { } ) {
107+ function useGovernor ( initialState = { } , contract = { } ) {
38108 if (
39109 ! contract ||
40110 ( typeof contract !== "object" && typeof contract !== "function" )
@@ -45,15 +115,11 @@ export function useGovernor(initialState = {}, contract = {}) {
45115 ) ;
46116 }
47117
48- const [ state , dispatch ] = useReducer (
49- ( state , action ) => action . newState ,
50- initialState
51- ) ;
118+ const [ state , dispatch ] = useReducer ( reducer , initialState ) ;
52119
53- const hookActions = useMemo ( ( ) => new HookActions ( contract , dispatch ) , [
54- contract
55- ] ) ;
56- hookActions . __state = state ;
120+ const actions = useMemo ( ( ) => createActions ( contract , dispatch ) , [ contract ] ) ;
57121
58- return [ state , hookActions ] ;
122+ return [ state , actions ] ;
59123}
124+
125+ export { useGovernor } ;
0 commit comments