Skip to content

Commit 51c84cd

Browse files
committed
main 🧊 rework create selector context
1 parent 1d2b843 commit 51c84cd

File tree

6 files changed

+87
-3
lines changed

6 files changed

+87
-3
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {
2+
createContext,
3+
createElement,
4+
startTransition,
5+
use,
6+
useMemo,
7+
useRef,
8+
useState
9+
} from 'react';
10+
import { useEvent, useIsomorphicLayoutEffect } from '@/hooks';
11+
const createProvider = (originalProvider) => {
12+
const Provider = (props) => {
13+
const valueRef = useRef(props.value);
14+
const contextValue = useMemo(
15+
() => ({
16+
value: valueRef,
17+
listeners: new Set()
18+
}),
19+
[]
20+
);
21+
useIsomorphicLayoutEffect(() => {
22+
if (!Object.is(valueRef.current, props.value)) {
23+
valueRef.current = props.value;
24+
startTransition(() => {
25+
contextValue.listeners.forEach((listener) => {
26+
listener(valueRef.current);
27+
});
28+
});
29+
}
30+
}, [props.value]);
31+
return createElement(originalProvider, { value: contextValue }, props.children);
32+
};
33+
return Provider;
34+
};
35+
const createReactiveContextSelector = (Context, selector, options = {}) => {
36+
const context = use(Context);
37+
if (!context && options.strict) {
38+
throw new Error(`Context hook ${options.name} must be used inside a Provider`);
39+
}
40+
const [value, setValue] = useState({
41+
selected: selector(context.value.current),
42+
value: context.value.current
43+
});
44+
const dispatch = useEvent((newValue) => {
45+
setValue((prevValue) => {
46+
if (Object.is(prevValue.value, newValue)) return prevValue;
47+
const newSelected = selector(newValue);
48+
if (Object.is(prevValue.selected, newSelected)) return prevValue;
49+
return { value: newValue, selected: newSelected };
50+
});
51+
});
52+
useIsomorphicLayoutEffect(() => {
53+
context.listeners.add(dispatch);
54+
return () => {
55+
context.listeners.delete(dispatch);
56+
};
57+
}, [context.listeners]);
58+
return value.selected;
59+
};
60+
/**
61+
* @name createReactiveContext
62+
* @description - Creates a typed context selector with optimized updates for state selection
63+
* @category Helpers
64+
*
65+
* @template Value - The type of value that will be stored in the context
66+
* @param {Value | undefined} [defaultValue] - Default value for the context
67+
* @param {CreateReactiveContextOptions<Value>} [options] - Additional options for context creation
68+
* @returns {CreateReactiveContextReturn<Value>} Object containing context utilities and components
69+
*
70+
* @example
71+
* const { Provider, useSelector, instance } = createReactiveContext<number>(0);
72+
*/
73+
export const createReactiveContext = (defaultValue = undefined, options = {}) => {
74+
const Context = createContext({
75+
value: { current: defaultValue },
76+
listeners: new Set()
77+
});
78+
const Provider = createProvider(Context.Provider);
79+
Context.displayName = options.name;
80+
function useSelector(selector) {
81+
return createReactiveContextSelector(Context, selector ?? ((state) => state), options);
82+
}
83+
return { instance: Context, Provider, useSelector };
84+
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export * from './createContext/createContext';
2-
export * from './createContextSelector/createReactiveContext';
2+
export * from './createReactiveContext/createReactiveContext';
33
export * from './createStore/createStore';

β€Žpackages/core/src/helpers/createContextSelector/createReactiveContext.ts renamed to β€Žpackages/core/src/helpers/createReactiveContext/createReactiveContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const createReactiveContextSelector = <Value, Selected>(
113113
* @template Value - The type of value that will be stored in the context
114114
* @param {Value | undefined} [defaultValue] - Default value for the context
115115
* @param {CreateReactiveContextOptions<Value>} [options] - Additional options for context creation
116-
* @returns {CreateContextSelectorReturn<Value>} Object containing context utilities and components
116+
* @returns {CreateReactiveContextReturn<Value>} Object containing context utilities and components
117117
*
118118
* @example
119119
* const { Provider, useSelector, instance } = createReactiveContext<number>(0);

β€Žpackages/core/src/helpers/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export * from './createContext/createContext';
2-
export * from './createContextSelector/createReactiveContext';
2+
export * from './createReactiveContext/createReactiveContext';
33
export * from './createStore/createStore';

0 commit comments

Comments
Β (0)