Skip to content
This repository was archived by the owner on Feb 14, 2023. It is now read-only.

Commit e0b4a66

Browse files
committed
resolves #73
1 parent 583290a commit e0b4a66

14 files changed

+383
-24
lines changed

Provider.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,28 +74,40 @@ Provider.addCallback(callbackFunction);
7474

7575
Provider.addReducer('name', reducerFunction);
7676

77+
Provider.getDispatch();
78+
7779
Provider.getGlobal();
7880

7981
Provider.removeCallback(callbackFunction);
8082

8183
Provider.resetGlobal();
8284

8385
Provider.setGlobal({ x: 1 });
86+
87+
Provider.useDispatch();
88+
89+
Provider.useDispatch('name');
90+
91+
Provider.useDispatch(reducerFunction);
92+
93+
Provider.useGlobal();
94+
95+
Provider.useGlobal('property');
8496
```
8597

8698
## useGlobal and withGlobal
8799

88-
The `useGlobal` React Hook and `withGlobal` Higher-Order Component, unlike
89-
other helper methods, have access to the ReactN Context. Because of this, you
90-
do not need to use your `Provider`'s static method.
100+
The `useDispatch` and `useGlobal` React Hooks and `withGlobal` Higher-Order
101+
Component, unlike other helper methods, have access to the ReactN Context.
102+
Because of this, you do not need to use your `Provider`'s static method.
91103

92104
However, you may want to mix-and-match multiple global states in a single
93105
component. Because of this, you can forcefully override the Context with your
94106
`Provider`'s static method.
95107

96-
`Provider.useGlobal` and `Provider.withGlobal` behave exactly like their
97-
respective helper functions, except they ignore the existing ReactN Context and
98-
use your `Provider` instead.
108+
`Provider.useDispatch`, `Provider.useGlobal`, and `Provider.withGlobal` behave
109+
exactly like their respective helper functions, except they ignore the existing
110+
ReactN Context and use your `Provider` instead.
99111

100112
Below is an example component that uses four different global states at once.
101113

@@ -131,12 +143,12 @@ function MyComponent({ x4 }) {
131143

132144
// Ignore Provider1 and map Provider4's global state to props.
133145
const MyComponentWithGlobal = Provider4.withGlobal(
134-
({ x }) => ({ x4: x })
146+
({ x }) => ({ x4: x }),
135147
);
136148

137149
ReactDOM.render(
138150
<Provider1>
139151
<MyComponentWithGlobal />
140-
</Provider1>
152+
</Provider1>,
141153
);
142154
```

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ with deeply nested objects, a
8585
* [Helper Functions](#helper-functions)
8686
* [addCallback](#addcallback)
8787
* [addReducer](#addreducer)
88+
* [getDispatch](#getdispatch)
8889
* [getGlobal](#getglobal)
8990
* [removeCallback](#removecallback)
9091
* [resetGlobal](#resetglobal)
@@ -438,11 +439,30 @@ addReducer('addSubtract', async (global, dispatch, i, j) => {
438439
are reducers. `addReducers` is just a convenient shorthand for calling
439440
`addReducer` multiple times.
440441

442+
##### getDispatch
443+
444+
Use `getDispatch` to return an object of the global dispatch functions. You
445+
only want to use this in helper libraries, and _not_ in Components. Components
446+
should use `useDispatch` or `this.dispatch`.
447+
448+
`getDispatch` has no parameters.
449+
450+
```JavaScript
451+
import { getDispatch } from 'reactn';
452+
453+
// Access this.dispatch.reducerName outside of a Component.
454+
class HelperLibrary {
455+
getDispatcherFunction() {
456+
return getDispatch().reducerName;
457+
}
458+
}
459+
```
460+
441461
##### getGlobal
442462

443463
Use `getGlobal` to return a current snapshot of the global state. You only want
444464
to use this in helper libraries, and _not_ in Components. Components should use
445-
`useGlobal` or `this.global` to insure that they re-render when the global
465+
`useGlobal` or `this.global` to ensure that they re-render when the global
446466
state changes. `getGlobal` will not cause a Component reliant on the global
447467
state to re-render, nor will it cause a library function to re-execute. It does
448468
nothing more than return a current snapshot of the global state.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "reactn",
3-
"version": "2.0.0",
3+
"version": "2.0.1",
44
"author": "Charles Stover <reactn@charlesstover.com>",
55
"description": "React, but with built-in global state management.",
66
"homepage": "https://github.com/CharlesStover/reactn#readme",

src/create-provider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,15 @@ export default function createProvider<
128128
return setGlobal<G>(globalStateManager, newGlobalState, callback);
129129
}
130130

131+
public static useDispatch(): Dispatchers<G, R>;
131132
public static useDispatch<A extends any[] = any[]>(
132133
reducer: Reducer<G, R, A>,
133134
): Dispatcher<G, A>;
134135
public static useDispatch<K extends keyof R = keyof R>(
135136
reducer: K,
136137
): Dispatcher<G, ExtractArguments<R[K]>>;
137138
public static useDispatch<K extends keyof R = keyof R, A extends any[] = any[]>(
138-
reducer: K | Reducer<G, R, A>,
139+
reducer?: K | Reducer<G, R, A>,
139140
): UseDispatch<G, R, K, A> {
140141

141142
// TypeScript required this be an if-else block with the exact same

src/get-dispatch.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Reducers, State } from '../default';
2+
import { Dispatchers } from './typings/reducer';
3+
import GlobalStateManager from './global-state-manager';
4+
5+
6+
7+
export default function getDispatch<
8+
G extends {} = State,
9+
R extends {} = Reducers,
10+
>(
11+
globalStateManager: GlobalStateManager<G, R>,
12+
): Dispatchers<G, R> {
13+
return globalStateManager.dispatchers;
14+
};

src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import addReducers from './add-reducers';
77
import createProvider, { ReactNProvider } from './create-provider';
88
import reactn from './decorator';
99
import defaultGlobalStateManager from './default-global-state-manager';
10+
import getDispatch from './get-dispatch';
1011
import getGlobal from './get-global';
1112
import { NewGlobalState } from './global-state-manager';
1213
import removeCallback from './remove-callback';
@@ -16,6 +17,7 @@ import Callback from './typings/callback';
1617
import Reducer, {
1718
AdditionalReducers,
1819
Dispatcher,
20+
Dispatchers,
1921
ExtractArguments,
2022
} from './typings/reducer';
2123
import useDispatch from './use-dispatch';
@@ -54,6 +56,9 @@ interface ReactN extends TypeOfReact {
5456

5557
default: ReactN;
5658

59+
getDispatch<G extends {} = State, R extends {} = Reducers>(
60+
): Dispatchers<G, R>;
61+
5762
getGlobal<G extends {} = State>(): G;
5863

5964
PureComponent: typeof ReactNPureComponent;
@@ -69,6 +74,9 @@ interface ReactN extends TypeOfReact {
6974
callback?: Callback<G>,
7075
): Promise<G>;
7176

77+
useDispatch<G extends {} = State, R extends {} = Reducers>(
78+
): Dispatchers<G, R>;
79+
7280
useDispatch<G extends {} = State, R extends {} = Reducers, A extends any[] = any[]>(
7381
reducer: Reducer<G, R, A>,
7482
): Dispatcher<G, A>;
@@ -113,6 +121,7 @@ export = Object.assign(reactn, React, {
113121
Component: ReactNComponent,
114122
createProvider,
115123
default: reactn,
124+
getDispatch: getDispatch.bind(null, defaultGlobalStateManager),
116125
getGlobal: getGlobal.bind(null, defaultGlobalStateManager),
117126
PureComponent: ReactNPureComponent,
118127
removeCallback: removeCallback.bind(null, defaultGlobalStateManager),

src/use-dispatch.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { Reducers, State } from '../default';
33
import Context from './context';
44
import defaultGlobalStateManager from './default-global-state-manager';
55
import GlobalStateManager from './global-state-manager';
6-
import Reducer, { Dispatcher, ExtractArguments } from './typings/reducer';
6+
import Reducer, {
7+
Dispatcher,
8+
Dispatchers,
9+
ExtractArguments,
10+
} from './typings/reducer';
711
import REACT_HOOKS_ERROR from './utils/react-hooks-error';
812

913

@@ -13,17 +17,25 @@ export type UseDispatch<
1317
R extends {} = Reducers,
1418
K extends keyof R = keyof R,
1519
A extends any[] = any[]
16-
> = Dispatcher<G, A> | Dispatcher<G, ExtractArguments<R[K]>>;
20+
> = Dispatcher<G, A> | Dispatcher<G, ExtractArguments<R[K]>> | Dispatchers<G, R>;
1721

1822

1923

24+
// useGlobal()
25+
export default function useDispatch<
26+
G extends {} = State,
27+
R extends {} = Reducers,
28+
>(
29+
overrideGlobalStateManager: GlobalStateManager<G, R> | null,
30+
): Dispatchers<G, R>;
31+
2032
// useDispatch(Function)
2133
export default function useDispatch<
2234
G extends {} = State,
2335
R extends {} = Reducers,
2436
A extends any[] = any[],
2537
>(
26-
overrideGlobalStateManager: GlobalStateManager<G, any> | null,
38+
overrideGlobalStateManager: GlobalStateManager<G, R> | null,
2739
reducer: Reducer<G, R, A>,
2840
): Dispatcher<G, A>;
2941

@@ -45,7 +57,7 @@ export default function useDispatch<
4557
A extends any[] = any[],
4658
>(
4759
overrideGlobalStateManager: GlobalStateManager<G, R> | null,
48-
reducer: K | Reducer<G, R, A>,
60+
reducer?: K | Reducer<G, R, A>,
4961
): UseDispatch<G, R, K, A> {
5062

5163
// Require hooks.
@@ -59,6 +71,11 @@ export default function useDispatch<
5971
(useContext(Context) as GlobalStateManager<G, R>) ||
6072
(defaultGlobalStateManager as GlobalStateManager<G, R>);
6173

74+
// Return all dispatchers.
75+
if (typeof reducer === 'undefined') {
76+
return globalStateManager.dispatchers;
77+
}
78+
6279
// Use a custom reducer.
6380
if (typeof reducer === 'function') {
6481
return globalStateManager.createDispatcher(reducer);

tests/context/use-dispatch-string.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import ReactN = require('../../src/index');
22
import createProvider, { ReactNProvider } from '../../src/create-provider';
33
import defaultGlobalStateManager from '../../src/default-global-state-manager';
4-
import { Dispatcher } from '../../src/typings/reducer';
4+
import { Dispatcher, ExtractArguments } from '../../src/typings/reducer';
55
import HookTest from '../utils/hook-test';
66
import { G, INITIAL_REDUCERS, INITIAL_STATE, R } from '../utils/initial';
77
import { hasContext } from '../utils/react-version';
88
import spyOn from '../utils/spy-on-global-state-manager';
99

1010

1111

12-
type A = string[];
12+
type A = ExtractArguments<R[typeof REDUCER]>;
1313

1414
type P = [ keyof R ];
1515

1616
type V = Dispatcher<G, A>;
1717

1818

1919

20-
const ARGS: string[] = [ 'te', 'st' ];
20+
const ARGS: A = [ 'te', 'st' ];
2121

2222
const EMPTY_STATE: {} = Object.create(null);
2323

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import ReactN = require('../../src/index');
2+
import createProvider, { ReactNProvider } from '../../src/create-provider';
3+
import defaultGlobalStateManager from '../../src/default-global-state-manager';
4+
import {
5+
Dispatcher,
6+
Dispatchers,
7+
ExtractArguments,
8+
} from '../../src/typings/reducer';
9+
import HookTest from '../utils/hook-test';
10+
import { G, INITIAL_REDUCERS, INITIAL_STATE, R } from '../utils/initial';
11+
import { hasContext } from '../utils/react-version';
12+
import spyOn from '../utils/spy-on-global-state-manager';
13+
14+
15+
16+
type A = ExtractArguments<R[typeof REDUCER]>;
17+
18+
type P = [ ];
19+
20+
type V = Dispatchers<G, R>;
21+
22+
23+
24+
const ARGS: A = [ 'te', 'st' ];
25+
26+
const EMPTY_STATE: {} = Object.create(null);
27+
28+
const Provider: ReactNProvider<G, R> = createProvider<G, R>(
29+
INITIAL_STATE,
30+
INITIAL_REDUCERS,
31+
);
32+
33+
const REDUCER: keyof R = 'append';
34+
35+
const REDUCER_NAMES: string[] = Object.keys(INITIAL_REDUCERS);
36+
37+
const STATE_CHANGE: Partial<G> = INITIAL_REDUCERS[REDUCER](
38+
INITIAL_STATE,
39+
Provider.dispatch,
40+
...ARGS,
41+
);
42+
43+
const NEW_STATE: G = {
44+
...INITIAL_STATE,
45+
...STATE_CHANGE,
46+
};
47+
48+
REDUCER_NAMES.sort();
49+
50+
51+
52+
describe('Context useDispatch()', (): void => {
53+
54+
// If Context is not supported,
55+
if (!hasContext) {
56+
return;
57+
}
58+
59+
let dispatchers: Dispatchers<G, R>;
60+
let testUseDispatch: HookTest<P, V>;
61+
const spy = spyOn('set');
62+
63+
beforeEach((): void => {
64+
testUseDispatch =
65+
new HookTest<P, V>(
66+
(): V => ReactN.useDispatch<G, R>()
67+
)
68+
.addParent(Provider);
69+
testUseDispatch.render();
70+
dispatchers = testUseDispatch.value;
71+
});
72+
73+
afterEach((): void => {
74+
Provider.reset();
75+
});
76+
77+
78+
it('should return an object', (): void => {
79+
expect(typeof dispatchers).toBe('object');
80+
});
81+
82+
it('should return the global dispatch object', async (): Promise<void> => {
83+
const DISPATCHER_NAMES: string[] = Object.keys(dispatchers);
84+
DISPATCHER_NAMES.sort();
85+
expect(DISPATCHER_NAMES).toEqual(REDUCER_NAMES);
86+
});
87+
88+
describe('the returned dispatch object values', (): void => {
89+
90+
let dispatch: Dispatcher<G, A>;
91+
beforeEach((): void => {
92+
dispatch = dispatchers[REDUCER];
93+
});
94+
95+
it('should call GlobalStateManager.set', async (): Promise<void> => {
96+
await dispatch(...ARGS);
97+
expect(spy.set).toHaveBeenCalledTimes(1);
98+
expect(spy.set).toHaveBeenCalledWith(STATE_CHANGE);
99+
});
100+
101+
it('should update the Context global state', async (): Promise<void> => {
102+
await dispatch(...ARGS);
103+
expect(Provider.global).toEqual(NEW_STATE);
104+
});
105+
106+
it('should not update the default global state', async (): Promise<void> => {
107+
await dispatch(...ARGS);
108+
expect(defaultGlobalStateManager.state).toStrictEqual(EMPTY_STATE);
109+
});
110+
});
111+
112+
});

0 commit comments

Comments
 (0)