Skip to content

Commit 1d8f869

Browse files
Added StateContext to library
1 parent c3956e4 commit 1d8f869

File tree

10 files changed

+226
-31
lines changed

10 files changed

+226
-31
lines changed

package-lock.json

Lines changed: 50 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "suspense-service",
3-
"version": "0.2.4",
3+
"version": "0.2.5",
44
"description": "Suspense integration library for React",
55
"repository": "github:patrickroberts/suspense-service",
66
"main": "dst/cjs/suspense-service.js",
@@ -28,15 +28,15 @@
2828
"@babel/preset-react": "^7.12.10",
2929
"@rollup/plugin-commonjs": "^15.1.0",
3030
"@rollup/plugin-node-resolve": "^9.0.0",
31-
"@types/jest": "^26.0.19",
31+
"@types/jest": "^26.0.20",
3232
"@types/react": "^16.14.2",
3333
"@types/react-dom": "^16.9.10",
3434
"@types/react-test-renderer": "^16.9.4",
3535
"@typescript-eslint/eslint-plugin": "4.11.1",
3636
"@typescript-eslint/parser": "4.11.1",
3737
"@wessberg/rollup-plugin-ts": "^1.3.8",
3838
"concurrently": "^5.3.0",
39-
"eslint": "^7.16.0",
39+
"eslint": "^7.17.0",
4040
"eslint-config-airbnb": "^18.2.1",
4141
"eslint-plugin-import": "^2.22.1",
4242
"eslint-plugin-jest": "^24.1.3",
@@ -49,11 +49,11 @@
4949
"react-dom": "^16.14.0",
5050
"react-test-renderer": "^16.14.0",
5151
"rimraf": "^3.0.2",
52-
"rollup": "^2.35.1",
52+
"rollup": "^2.36.1",
5353
"rollup-plugin-terser": "^7.0.2",
5454
"ts-jest": "^26.4.4",
55-
"typedoc": "^0.20.0",
56-
"typedoc-plugin-markdown": "^3.2.1",
55+
"typedoc": "^0.20.14",
56+
"typedoc-plugin-markdown": "^3.4.0",
5757
"typedoc-plugin-sourcefile-url": "^1.0.6",
5858
"typescript": "^4.1.3"
5959
},
@@ -70,6 +70,7 @@
7070
"library",
7171
"react",
7272
"service",
73+
"stateful",
7374
"suspense",
7475
"typescript"
7576
],

src/IdContext/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function createIdContext<T>(defaultValue: T): IdContext<T> {
3838
}
3939

4040
/**
41-
* Consumes a value from a {@link IdContextProvider}
41+
* Consumes a value from an {@link IdContextProvider}
4242
* @param context the {@link IdContext} to use
4343
* @param id the {@link IdContextProviderProps.id | IdContextProvider id} to use
4444
*/

src/StateContext/Consumer/Props.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Dispatch, ReactNode, SetStateAction } from 'react';
2+
import Id from '../../IdContext/Id';
3+
4+
export default interface StateConsumerProps<T> {
5+
/**
6+
* The {@link StateProvider} to use
7+
* @default null
8+
*/
9+
id?: Id;
10+
children: (value: T, setState: Dispatch<SetStateAction<T>>) => ReactNode;
11+
}
12+
13+
/** @ignore */
14+
export const defaultProps = {
15+
id: null,
16+
};

src/StateContext/Consumer/index.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { ComponentType, memo, useCallback, useMemo } from 'react';
2+
import IdContext from '../../IdContext';
3+
import State from '../State';
4+
import StateContextConsumerProps, { defaultProps } from './Props';
5+
6+
type StateContextConsumer<T> = ComponentType<StateContextConsumerProps<T>>;
7+
8+
export default StateContextConsumer;
9+
export { StateContextConsumerProps };
10+
11+
/** @ignore */
12+
export function createStateContextConsumer<T>(
13+
{ Consumer }: IdContext<State<T>>,
14+
): StateContextConsumer<T> {
15+
const StateConsumer: StateContextConsumer<T> = ({ id, children }) => {
16+
const render = useCallback(
17+
([state, setState]: State<T>) => children(state, setState),
18+
[children],
19+
);
20+
21+
return useMemo(() => (
22+
<Consumer id={id}>{render}</Consumer>
23+
), [id, render]);
24+
};
25+
26+
StateConsumer.defaultProps = defaultProps;
27+
28+
return memo(StateConsumer, (prev, next) => (
29+
Object.is(prev.id, next.id)
30+
&& Object.is(prev.children, next.children)
31+
));
32+
}

src/StateContext/Provider/Props.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ReactNode } from 'react';
2+
import Id from '../../IdContext/Id';
3+
import Reset from '../../State/Reset';
4+
5+
export default interface StateContextProviderProps<T> {
6+
/**
7+
* The initial value to provide
8+
*/
9+
value: T;
10+
/**
11+
* The key that identifies the {@link StateContextProvider} to be consumed
12+
* @default null
13+
*/
14+
id?: Id;
15+
/**
16+
* @default null
17+
*/
18+
children?: ReactNode;
19+
/**
20+
* The reset function when {@link StateProviderProps.value | value} updates
21+
*/
22+
reset?: Reset<T>;
23+
}
24+
25+
/** @ignore */
26+
export const defaultProps = {
27+
id: null,
28+
children: null,
29+
};

src/StateContext/Provider/index.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { ComponentType, memo, useMemo } from 'react';
2+
import IdContext from '../../IdContext';
3+
import useResetState from '../../State/useResetState';
4+
import State from '../State';
5+
import StateContextProviderProps, { defaultProps } from './Props';
6+
7+
type StateContextProvider<T> = ComponentType<StateContextProviderProps<T>>;
8+
9+
export default StateContextProvider;
10+
export { StateContextProviderProps };
11+
12+
/** @ignore */
13+
export function createStateContextProvider<T>(
14+
StateContext: IdContext<State<T>>,
15+
): StateContextProvider<T> {
16+
const { Provider } = StateContext;
17+
const StateProvider: StateContextProvider<T> = ({ value, id, children, reset }) => {
18+
const state = useResetState(value, reset);
19+
20+
return useMemo(() => (
21+
<Provider value={state} id={id}>{children}</Provider>
22+
), [state, id, children]);
23+
};
24+
25+
StateProvider.defaultProps = defaultProps;
26+
27+
return memo(StateProvider, (prev, next) => (
28+
Object.is(prev.value, next.value)
29+
&& Object.is(prev.id, next.id)
30+
&& Object.is(prev.children, next.children)
31+
));
32+
}

src/StateContext/State.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Dispatch, SetStateAction } from 'react';
2+
3+
/**
4+
* A stateful value and a function to update it
5+
*/
6+
type State<T> = [T, Dispatch<SetStateAction<T>>];
7+
8+
export default State;

src/StateContext/index.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import Id from '../IdContext/Id';
2+
import State from './State';
3+
import IdContext, { createIdContext, useIdContext } from '../IdContext';
4+
import StateContextConsumer, { createStateContextConsumer } from './Consumer';
5+
import StateContextProvider, { createStateContextProvider } from './Provider';
6+
7+
/**
8+
* A privately scoped unique symbol for accessing {@link StateContext} internal {@link State}
9+
* @internal
10+
*/
11+
const kState = Symbol('kState');
12+
13+
/**
14+
* A State Context with support for multiple keyed values
15+
*/
16+
export default interface StateContext<T> {
17+
Consumer: StateContextConsumer<T>;
18+
Provider: StateContextProvider<T>;
19+
/** @internal */
20+
[kState]: IdContext<State<T>>;
21+
}
22+
23+
/**
24+
* Creates a State Context for providing a stateful value and a function to update it.
25+
* @param defaultValue the value consumed if no {@link StateContextProvider} is in scope and the
26+
* {@link StateContextConsumerProps.id | consumer `id`} is `null`
27+
*/
28+
export function createStateContext<T>(defaultValue: T): StateContext<T> {
29+
const StateContext = createIdContext<State<T>>(
30+
[defaultValue, () => undefined],
31+
);
32+
33+
return {
34+
Consumer: createStateContextConsumer(StateContext),
35+
Provider: createStateContextProvider(StateContext),
36+
[kState]: StateContext,
37+
};
38+
}
39+
40+
/**
41+
* Consumes a stateful value from a {@link StateContextProvider}, and a function to update it
42+
* @param context the {@link StateContext} to use
43+
* @param id the {@link StateContextProviderProps.id | StateContextProvider id} to use
44+
*/
45+
export function useStateContext<T>(context: StateContext<T>, id: Id = null): State<T> {
46+
return useIdContext(context[kState], id);
47+
}

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@ export { default as IdContext, createIdContext, useIdContext } from './IdContext
55
export { default as ServiceConsumer, ServiceConsumerProps } from './Service/Consumer';
66
export { default as ServiceProvider, ServiceProviderProps } from './Service/Provider';
77
export { default as Service, Handler, createService, useService, useServiceState } from './Service';
8+
export { default as State } from './StateContext/State';
9+
export { default as StateContextConsumer, StateContextConsumerProps } from './StateContext/Consumer';
10+
export { default as StateContextProvider, StateContextProviderProps } from './StateContext/Provider';
11+
export { default as StateContext, createStateContext, useStateContext } from './StateContext';
812
export { default as Reset } from './State/Reset';

0 commit comments

Comments
 (0)