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

Commit 3aec704

Browse files
committed
resolves #90
resolves #85
1 parent 6e8f269 commit 3aec704

15 files changed

+345
-168
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ reducer is the ReactN global state object.
5151
You use `dispatch = useDispatch(reducerName)` to use a reducer that was added
5252
by the `addReducer` helper function.
5353

54+
You use `dispatch = useDispatch(reducerFunction, property)` or
55+
`[ value, dispatch ] = useDispatch(reducerFunction, property)` to apply a
56+
reducer specifically to a global state property. This is very similar to
57+
React's native `useReducer` functionality.
58+
5459
#### Class Components
5560

5661
Global state in class components behaves exactly like local state!

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "reactn",
3-
"version": "2.1.2",
3+
"version": "2.1.3",
44
"author": "Charles Stover <[email protected]>",
55
"description": "React, but with built-in global state management.",
66
"homepage": "https://github.com/CharlesStover/reactn#readme",
@@ -50,5 +50,10 @@
5050
"ts-jest": "^24.0.1",
5151
"ts-node": "^8.0.2",
5252
"typescript": "^3.3.1"
53+
},
54+
"peerDependencies": {
55+
"@types/react": "^16.8.0",
56+
"react": "^16.3.0",
57+
"react-dom": "^16.3.0"
5358
}
5459
}

src/create-provider.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import * as React from 'react';
22
import { Reducers, State } from '../default';
33
import Callback from '../types/callback';
4-
import Dispatcher, { ExtractArguments } from '../types/dispatcher';
4+
import Dispatcher, {
5+
ExtractArguments,
6+
PropertyDispatcher,
7+
} from '../types/dispatcher';
58
import Dispatchers from '../types/dispatchers';
69
import NewGlobalState from '../types/new-global-state';
710
import ReactNProvider from '../types/provider';
@@ -97,23 +100,23 @@ export default function _createProvider<
97100
public static useDispatch<A extends any[] = any[]>(
98101
reducer: Reducer<G, R, A>,
99102
): Dispatcher<G, A>;
100-
public static useDispatch<A extends any[] = any[], P extends keyof G = keyof G>(
101-
reducer: PropertyReducer<G, A, P>,
103+
public static useDispatch<P extends keyof G = keyof G, A extends any[] = any[]>(
104+
reducer: PropertyReducer<G, P, A>,
102105
property: P,
103-
): Dispatcher<G, A>;
106+
): PropertyDispatcher<G, P, A>;
104107
public static useDispatch<K extends keyof R = keyof R>(
105108
reducer: K,
106109
): Dispatcher<G, ExtractArguments<R[K]>>;
107-
public static useDispatch<K extends keyof R = keyof R, A extends any[] = any[], P extends keyof G = keyof G>(
108-
reducer?: K | Reducer<G, R, A> | PropertyReducer<G, A, P>,
110+
public static useDispatch<P extends keyof G = keyof G, K extends keyof R = keyof R, A extends any[] = any[]>(
111+
reducer?: K | Reducer<G, R, A> | PropertyReducer<G, P, A>,
109112
property?: P,
110-
): UseDispatch<G, R, K, A> {
113+
): UseDispatch<G, R, P, K, A> {
111114

112115
// TypeScript required these synonymous function calls be separate.
113116
// Each call has its own generics, pleasing the TypeScript overlord.
114117
if (typeof reducer === 'function') {
115118
if (isPropertyReducer(reducer, property)) {
116-
return useDispatch<G, R, A, P>(
119+
return useDispatch<G, R, P, A>(
117120
globalStateManager,
118121
reducer,
119122
property,

src/global-state-manager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,9 @@ export default class GlobalStateManager<
263263

264264
// Remove this property listener from the global state.
265265
for (const propertyListeners of this.propertyListeners.values()) {
266-
removed = removed || propertyListeners.delete(propertyListener);
266+
if (propertyListeners.delete(propertyListener)) {
267+
removed = true;
268+
}
267269
}
268270

269271
return removed;

src/use-dispatch.ts

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { useContext } from 'react';
2+
import useForceUpdate from 'use-force-update';
23
import { Reducers, State } from '../default';
3-
import Dispatcher, { ExtractArguments } from '../types/dispatcher';
4+
import Dispatcher, {
5+
ExtractArguments,
6+
PropertyDispatcher,
7+
} from '../types/dispatcher';
48
import Dispatchers from '../types/dispatchers';
59
import Reducer, { PropertyReducer } from '../types/reducer';
610
import Context from './context';
@@ -14,9 +18,16 @@ import REACT_HOOKS_ERROR from './utils/react-hooks-error';
1418
export type UseDispatch<
1519
G extends {} = State,
1620
R extends {} = Reducers,
21+
P extends keyof G = keyof G,
1722
K extends keyof R = keyof R,
18-
A extends any[] = any[]
19-
> = Dispatcher<G, A> | Dispatcher<G, ExtractArguments<R[K]>> | Dispatchers<G, R>;
23+
A extends any[] = any[],
24+
> =
25+
| Dispatcher<G, A>
26+
| Dispatcher<G, ExtractArguments<R[K]>>
27+
| Dispatchers<G, R>
28+
| PropertyDispatcher<G, P, A>;
29+
30+
type VoidFunction = () => void;
2031

2132

2233

@@ -42,13 +53,13 @@ export default function _useDispatch<
4253
export default function _useDispatch<
4354
G extends {} = State,
4455
R extends {} = Reducers,
45-
A extends any[] = any[],
4656
P extends keyof G = keyof G,
57+
A extends any[] = any[],
4758
>(
4859
overrideGlobalStateManager: GlobalStateManager<G, R> | null,
49-
reducer: PropertyReducer<G, A, P>,
60+
reducer: PropertyReducer<G, P, A>,
5061
property: P,
51-
): Dispatcher<G, A>;
62+
): PropertyDispatcher<G, P, A>;
5263

5364
// useDispatch('name')
5465
export default function _useDispatch<
@@ -78,20 +89,22 @@ export default function _useDispatch<
7889
export default function _useDispatch<
7990
G extends {} = State,
8091
R extends {} = Reducers,
92+
P extends keyof G = keyof G,
8193
K extends keyof R = keyof R,
8294
A extends any[] = any[],
83-
P extends keyof G = keyof G,
8495
>(
8596
overrideGlobalStateManager: GlobalStateManager<G, R> | null,
86-
reducer?: K | Reducer<G, R, A> | PropertyReducer<G, A, P>,
97+
reducer?: K | Reducer<G, R, A> | PropertyReducer<G, P, A>,
8798
property?: P,
88-
): UseDispatch<G, R, K, A> {
99+
): UseDispatch<G, R, P, K, A> {
89100

90101
// Require hooks.
91102
if (!useContext) {
92103
throw REACT_HOOKS_ERROR;
93104
}
94105

106+
const forceUpdate: VoidFunction = useForceUpdate();
107+
95108
// Get the global state manager.
96109
const globalStateManager: GlobalStateManager<G, R> =
97110
overrideGlobalStateManager ||
@@ -109,6 +122,8 @@ export default function _useDispatch<
109122
// If this reducer mutates a specific property, create a dispatcher that
110123
// targets that property.
111124
if (isPropertyReducer(reducer, property)) {
125+
126+
// Create a middleware reducer specifically for handling this property.
112127
const newReducer: Reducer<G, R, A, Partial<G>> = (
113128
global: G,
114129
_dispatch: Dispatchers<G, R>,
@@ -118,7 +133,70 @@ export default function _useDispatch<
118133
newGlobalState[property] = reducer(global[property], ...args);
119134
return newGlobalState;
120135
};
121-
return globalStateManager.createDispatcher(newReducer);
136+
137+
// Pre-emptively set dispatcher to type PropertyDispatcher so that we can
138+
// add the tuple to it.
139+
const propertyDispatcher: PropertyDispatcher<G, P, A> =
140+
(
141+
globalStateManager.createDispatcher(newReducer, reducer.name)
142+
) as PropertyDispatcher<G, P, A>;
143+
144+
// [0] is the property value, with subscription.
145+
Object.defineProperty(propertyDispatcher, 0, {
146+
configurable: true,
147+
enumerable: true,
148+
get(): G[P] {
149+
globalStateManager.addPropertyListener(property, forceUpdate);
150+
return globalStateManager.state[property];
151+
},
152+
});
153+
154+
// [1] is the dispatcher itself.
155+
propertyDispatcher[1] = propertyDispatcher;
156+
157+
// Iterators must have a slice method.
158+
const propertyDispatcherSlice = (
159+
start?: number,
160+
end?: number
161+
): (G[P] | Dispatcher<G, A>)[] => {
162+
const values: (G[P] | Dispatcher<G, A>)[] =
163+
[ propertyDispatcher[0], propertyDispatcher[1] ];
164+
return values.slice.apply(values, [ start, end ]);
165+
};
166+
propertyDispatcher.slice = propertyDispatcherSlice;
167+
168+
// Iterators must have a Symbol.iterator property.
169+
const propertyDispatcherIterator =
170+
(): IterableIterator<G[P] | Dispatcher<G, A>> => {
171+
let index: number = 0;
172+
const propertyDispatcherIteratorNext =
173+
(): IteratorResult<Dispatcher<G, A> | G[P]> => {
174+
175+
// IteratorResult
176+
if (index < 2) {
177+
return {
178+
done: false,
179+
value: propertyDispatcher[index++],
180+
};
181+
}
182+
183+
// Done.
184+
index = 0;
185+
return {
186+
done: true,
187+
value: undefined,
188+
};
189+
};
190+
191+
// IterableIterator
192+
return {
193+
[Symbol.iterator]: propertyDispatcher[Symbol.iterator],
194+
next: propertyDispatcherIteratorNext,
195+
};
196+
};
197+
propertyDispatcher[Symbol.iterator] = propertyDispatcherIterator;
198+
199+
return propertyDispatcher;
122200
}
123201

124202
// If this reducer does not mutate a specific property, create it as is.

src/utils/component-will-unmount.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { ReactNComponentWillUnmount } from '../methods';
44

55

66

7-
type VoidFunction = () => void;
7+
// type VoidFunction = () => void;
88

99

1010

1111
// this.componentWillUnmount on instance
12+
/*
1213
export const componentWillUnmountInstance = <
1314
P extends {} = {},
1415
S extends {} = {},
@@ -28,6 +29,7 @@ export const componentWillUnmountInstance = <
2829
}
2930
return false;
3031
};
32+
*/
3133

3234
// this.componentWillUnmount on prototype
3335
export const componentWillUnmountPrototype = <
@@ -43,6 +45,7 @@ export const componentWillUnmountPrototype = <
4345
Object.getPrototypeOf(that);
4446
if (Object.prototype.hasOwnProperty.call(proto, 'componentWillUnmount')) {
4547
that.componentWillUnmount = (): void => {
48+
console.log('ReactN component unmounting.');
4649
ReactNComponentWillUnmount(that);
4750
proto.componentWillUnmount.bind(that)();
4851
};

src/utils/component-will-update.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { ReactNComponentWillUpdate } from '../methods';
44

55

66

7+
/*
78
type ComponentWillUpdate<P extends {} = {}, S extends {} = {}> =
89
(nextProps: P, nextState: S, context: any) => void;
10+
*/
911

1012

1113

1214
// this.componentWillUpdate on instance
15+
/*
1316
export const componentWillUpdateInstance = <
1417
P extends {} = {},
1518
S extends {} = {},
@@ -29,6 +32,7 @@ export const componentWillUpdateInstance = <
2932
}
3033
return false;
3134
};
35+
*/
3236

3337
// this.componentWillUpdate on prototype
3438
export const componentWillUpdatePrototype = <

src/utils/is-property-reducer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import Reducer, { PropertyReducer } from '../../types/reducer';
66
export default function isPropertyReducer<
77
G extends {} = State,
88
R extends {} = Reducers,
9-
A extends any[] = any[],
109
P extends keyof G = keyof G,
10+
A extends any[] = any[],
1111
>(
12-
_reducer: Reducer<G, R, A> | PropertyReducer<G, A, P>,
12+
_reducer: Reducer<G, R, A> | PropertyReducer<G, P, A>,
1313
property?: keyof G,
14-
): _reducer is PropertyReducer<G, A, P> {
14+
): _reducer is PropertyReducer<G, P, A> {
1515
return typeof property !== 'undefined';
1616
};

tests/global-state-manager/remove-property-listener.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { G, INITIAL_STATE } from '../utils/initial';
44

55

66
const PROPERTY: keyof G = 'x';
7+
8+
const PROPERTY2: keyof G = 'y';
9+
710
const PROPERTY_LISTENER = (): void => { };
811

912

@@ -22,8 +25,18 @@ describe('GlobalStateManager.removePropertyListener', (): void => {
2225
expect(globalStateManager.removePropertyListener).toHaveLength(1);
2326
});
2427

25-
it('remove a property listener', (): void => {
28+
it('should remove a property listener', (): void => {
29+
globalStateManager.addPropertyListener(PROPERTY, PROPERTY_LISTENER);
30+
expect(globalStateManager.hasPropertyListener(PROPERTY_LISTENER))
31+
.toBe(true);
32+
globalStateManager.removePropertyListener(PROPERTY_LISTENER);
33+
expect(globalStateManager.hasPropertyListener(PROPERTY_LISTENER))
34+
.toBe(false);
35+
});
36+
37+
it('should remove more than 1 property listener', (): void => {
2638
globalStateManager.addPropertyListener(PROPERTY, PROPERTY_LISTENER);
39+
globalStateManager.addPropertyListener(PROPERTY2, PROPERTY_LISTENER);
2740
expect(globalStateManager.hasPropertyListener(PROPERTY_LISTENER))
2841
.toBe(true);
2942
globalStateManager.removePropertyListener(PROPERTY_LISTENER);

0 commit comments

Comments
 (0)