use-context-selector
Advanced tools
Comparing version 2.0.0-alpha.6 to 2.0.0-alpha.7
@@ -1,2 +0,2 @@ | ||
import{unstable_createMutableSource as e,createContext as r,memo as t,useContext as n,useCallback as o,unstable_useMutableSource as c,useRef as u,useMemo as s,createElement as i,useLayoutEffect as a,useEffect as d}from"react";import{unstable_runWithPriority as f,unstable_NormalPriority as l,unstable_UserBlockingPriority as v}from"scheduler";const p="undefined"==typeof window||/ServerSideRendering/.test(window.navigator&&window.navigator.userAgent)?d:a,w=Symbol(),m=Symbol(),h=new WeakMap,g=e=>e;function x(n){const o=e({current:n},()=>n),c=r({[w]:o});var a;return c.Provider=(a=c.Provider,t(({value:r,children:t})=>{const n=u({v:r,l:new Set});p(()=>{n.current.v=r,f(l,()=>{n.current.l.forEach(e=>e())})});const o=s(()=>({[w]:e(n,()=>n.current.v)}),[]);return i(a,{value:o},t)})),delete c.Consumer,c}const S=(e,r)=>{const t=e.current.l;return t.add(r),()=>t.delete(r)};function y(e,r=g){const{[w]:t}=n(e);if("production"!==process.env.NODE_ENV&&!t)throw new Error("This useContext requires special context for selector support");const u=o(e=>{const t=r(e.current.v);if("function"==typeof t){if(h.has(t))return h.get(t);const e={[m]:t};return h.set(t,e),e}return t},[r]),s=c(t,u,S);return s&&s[m]?s[m]:s}function C(e){return(...r)=>f(v,()=>e(...r))}export{x as createContext,y as useContext,C as wrapCallbackWithPriority}; | ||
import{createContext as r,unstable_createMutableSource as e,useContext as n,useCallback as t,unstable_useMutableSource as o,useRef as s,useMemo as c,createElement as u,useLayoutEffect as i,useEffect as l}from"react";import{unstable_NormalPriority as a,unstable_getCurrentPriorityLevel as p,unstable_runWithPriority as d,unstable_ImmediatePriority as f}from"scheduler";const v="undefined"==typeof window||/ServerSideRendering/.test(window.navigator&&window.navigator.userAgent)?l:i,w=Symbol(),S=Symbol(),h=Symbol(),m=new WeakMap,g=r=>r,x=r=>{const n={current:{v:-1,p:r,s:r,l:new Set}};return e(n,()=>n.current.v)};function y(n){const t=r({[w]:x(n),[S]:r=>r()});var o;return t.Provider=(o=t.Provider,({value:r,children:n})=>{const t=s({v:0,p:r,s:r,l:new Set}),i=s(a),l=c(()=>({[w]:e(t,()=>t.current.v),[S]:r=>(i.current=p(),d(f,r))}),[]);return v(()=>{null!==l[w]._workInProgressVersionSecondary?t.current.s=r:t.current.p=r,t.current.v+=1,d(i.current,()=>{t.current.l.forEach(r=>r())}),i.current=a}),u(o,{value:l},n)}),delete t.Consumer,t}const E=(r,e)=>{const n=r.current.l;return n.add(e),()=>n.delete(e)};function C(r,e=g){const{[w]:s}=n(r);if("production"!==process.env.NODE_ENV&&!s)throw new Error("This useContext requires special context for selector support");const c=t(r=>{const n=e(null!==s._workInProgressVersionSecondary?r.current.s:r.current.p);if("function"==typeof n){if(m.has(n))return m.get(n);const r={[h]:n};return m.set(n,r),r}return n},[e,s]),u=o(s,c,E);return u&&u[h]?u[h]:u}function N(r){const{[S]:e}=n(r);if("production"!==process.env.NODE_ENV&&!e)throw new Error("This useContext requires special context for selector support");return e}export{y as createContext,C as useContext,N as useContextUpdate}; | ||
//# sourceMappingURL=index.modern.js.map |
@@ -1,2 +0,2 @@ | ||
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("react"),require("scheduler")):"function"==typeof define&&define.amd?define(["exports","react","scheduler"],r):r((e=e||self).useContextSelector={},e.react,e.scheduler)}(this,function(e,r,t){var n="undefined"==typeof window||/ServerSideRendering/.test(window.navigator&&window.navigator.userAgent)?r.useEffect:r.useLayoutEffect,u=Symbol(),o=Symbol(),i=new WeakMap,c=function(e){return e},a=function(e,r){var t=e.current.l;return t.add(r),function(){return t.delete(r)}};e.createContext=function(e){var o,i,c=r.unstable_createMutableSource({current:e},function(){return e}),a=r.createContext(((o={})[u]=c,o));return a.Provider=(i=a.Provider,r.memo(function(e){var o,c=e.value,a=e.children,f=r.useRef(((o={}).v=c,o.l=new Set,o));n(function(){f.current.v=c,t.unstable_runWithPriority(t.unstable_NormalPriority,function(){f.current.l.forEach(function(e){return e()})})});var l=r.useMemo(function(){var e;return(e={})[u]=r.unstable_createMutableSource(f,function(){return f.current.v}),e},[]);return r.createElement(i,{value:l},a)})),delete a.Consumer,a},e.useContext=function(e,t){void 0===t&&(t=c);var n=r.useContext(e)[u];if("production"!==process.env.NODE_ENV&&!n)throw new Error("This useContext requires special context for selector support");var f=r.useCallback(function(e){var r=t(e.current.v);if("function"==typeof r){var n;if(i.has(r))return i.get(r);var u=((n={})[o]=r,n);return i.set(r,u),u}return r},[t]),l=r.unstable_useMutableSource(n,f,a);return l&&l[o]?l[o]:l},e.wrapCallbackWithPriority=function(e){return function(){var r=arguments;return t.unstable_runWithPriority(t.unstable_UserBlockingPriority,function(){return e.apply(void 0,[].slice.call(r))})}}}); | ||
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("react"),require("scheduler")):"function"==typeof define&&define.amd?define(["exports","react","scheduler"],r):r((e=e||self).useContextSelector={},e.react,e.scheduler)}(this,function(e,r,t){var n="undefined"==typeof window||/ServerSideRendering/.test(window.navigator&&window.navigator.userAgent)?r.useEffect:r.useLayoutEffect,u=Symbol(),o=Symbol(),i=Symbol(),c=new WeakMap,s=function(e){return e},a=function(e,r){var t=e.current.l;return t.add(r),function(){return t.delete(r)}};e.createContext=function(e){var i,c,s=r.createContext(((i={})[u]=function(e){var t={current:{v:-1,p:e,s:e,l:new Set}};return r.unstable_createMutableSource(t,function(){return t.current.v})}(e),i[o]=function(e){return e()},i));return s.Provider=(c=s.Provider,function(e){var i=e.value,s=e.children,a=r.useRef({v:0,p:i,s:i,l:new Set}),f=r.useRef(t.unstable_NormalPriority),l=r.useMemo(function(){var e;return(e={})[u]=r.unstable_createMutableSource(a,function(){return a.current.v}),e[o]=function(e){return f.current=t.unstable_getCurrentPriorityLevel(),t.unstable_runWithPriority(t.unstable_ImmediatePriority,e)},e},[]);return n(function(){null!==l[u]._workInProgressVersionSecondary?a.current.s=i:a.current.p=i,a.current.v+=1,t.unstable_runWithPriority(f.current,function(){a.current.l.forEach(function(e){return e()})}),f.current=t.unstable_NormalPriority}),r.createElement(c,{value:l},s)}),delete s.Consumer,s},e.useContext=function(e,t){void 0===t&&(t=s);var n=r.useContext(e)[u];if("production"!==process.env.NODE_ENV&&!n)throw new Error("This useContext requires special context for selector support");var o=r.useCallback(function(e){var r=t(null!==n._workInProgressVersionSecondary?e.current.s:e.current.p);if("function"==typeof r){var u;if(c.has(r))return c.get(r);var o=((u={})[i]=r,u);return c.set(r,o),o}return r},[t,n]),f=r.unstable_useMutableSource(n,o,a);return f&&f[i]?f[i]:f},e.useContextUpdate=function(e){var t=r.useContext(e)[o];if("production"!==process.env.NODE_ENV&&!t)throw new Error("This useContext requires special context for selector support");return t}}); | ||
//# sourceMappingURL=index.umd.js.map |
@@ -23,15 +23,13 @@ import { ComponentType } from 'react'; | ||
export declare function useContext<Value, Selected>(context: Context<Value>, selector: (value: Value) => Selected): Selected; | ||
declare type AnyCallback = (...args: any) => any; | ||
/** | ||
* A utility function to wrap a callback function with higher priority | ||
* This hook returns an update function that accepts a thunk function | ||
* | ||
* Use this for a callback that will change a value, | ||
* which will be fed into context provider. | ||
* Use this for a function that will change a value. | ||
* | ||
* @example | ||
* import { wrapCallbackWithPriority } from 'use-context-selector'; | ||
* import { useContextUpdate } from 'use-context-selector'; | ||
* | ||
* const wrappedCallback = wrapCallbackWithPriority(callback); | ||
* const update = useContextUpdate(); | ||
* update(() => setState(...)); | ||
*/ | ||
export declare function wrapCallbackWithPriority<Callback extends AnyCallback>(callback: Callback): Callback; | ||
export {}; | ||
export declare function useContextUpdate(context: Context<unknown>): <T>(thunk: () => T) => T; |
{ | ||
"name": "use-context-selector", | ||
"description": "React useContext with selector support in userland", | ||
"version": "2.0.0-alpha.6", | ||
"version": "2.0.0-alpha.7", | ||
"publishConfig": { | ||
@@ -6,0 +6,0 @@ "tag": "next" |
@@ -157,12 +157,11 @@ # use-context-selector | ||
### wrapCallbackWithPriority | ||
### useContextUpdate | ||
A utility function to wrap a callback function with higher priority | ||
This hook returns an update function that accepts a thunk function | ||
Use this for a callback that will change a value, | ||
which will be fed into context provider. | ||
Use this for a function that will change a value. | ||
#### Parameters | ||
- `callback` **Callback** | ||
- `context` **Context<any>** | ||
@@ -172,5 +171,6 @@ #### Examples | ||
```javascript | ||
import { wrapCallbackWithPriority } from 'use-context-selector'; | ||
import { useContextUpdate } from 'use-context-selector'; | ||
const wrappedCallback = wrapCallbackWithPriority(callback); | ||
const update = useContextUpdate(); | ||
update(() => setState(...)); | ||
``` | ||
@@ -181,3 +181,2 @@ | ||
- In order to stop propagation, `children` of a context provider has to be either created outside of the provider or memoized with `React.memo`. | ||
- Provider trigger re-renders only if the context value is referentially changed. | ||
- Neither context consumers or class components are supported. | ||
@@ -184,0 +183,0 @@ - The [stale props](https://react-redux.js.org/api/hooks#stale-props-and-zombie-children) issue can't be solved in userland. |
116
src/index.ts
@@ -13,3 +13,2 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
unstable_createMutableSource as createMutableSource, | ||
memo, | ||
useCallback, | ||
@@ -25,5 +24,6 @@ useContext as useContextOrig, | ||
import { | ||
unstable_UserBlockingPriority as UserBlockingPriority, | ||
unstable_ImmediatePriority as ImmediatePriority, | ||
unstable_NormalPriority as NormalPriority, | ||
unstable_runWithPriority as runWithPriority, | ||
unstable_getCurrentPriorityLevel as getCurrentPriorityLevel, | ||
} from 'scheduler'; | ||
@@ -39,8 +39,7 @@ | ||
const SOURCE_SYMBOL = Symbol(); | ||
const VALUE_PROP = 'v'; | ||
const LISTENERS_PROP = 'l'; | ||
const UPDATE_SYMBOL = Symbol(); | ||
const FUNCTION_SYNBOL = Symbol(); | ||
const FUNCTION_SYMBOL = Symbol(); | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
const functionMap = new WeakMap<Function, { [FUNCTION_SYNBOL]: Function }>(); | ||
const functionMap = new WeakMap<Function, { [FUNCTION_SYMBOL]: Function }>(); | ||
@@ -50,4 +49,12 @@ // @ts-ignore | ||
[SOURCE_SYMBOL]: any; | ||
[UPDATE_SYMBOL]: <T>(thunk: () => T) => T; | ||
}; | ||
type RefValue<Value> = MutableRefObject<{ | ||
v: number; // "v" = version | ||
p: Value; // "p" = primary value | ||
s: Value; // "s" = secondary value | ||
l: Set<() => void>; // "l" = listeners | ||
}>; | ||
export interface Context<Value> { | ||
@@ -60,18 +67,31 @@ Provider: ComponentType<{ value: Value }>; | ||
const RefProvider: FC<{ value: Value }> = ({ value, children }) => { | ||
const ref = useRef({ | ||
[VALUE_PROP]: value, | ||
[LISTENERS_PROP]: new Set<() => void>(), | ||
const ref: RefValue<Value> = useRef({ | ||
v: 0, // "v" = version | ||
p: value, // "p" = primary value | ||
s: value, // "s" = secondary value | ||
l: new Set<() => void>(), // "l" = listeners | ||
}); | ||
const priorityRef = useRef(NormalPriority); | ||
const contextValue = useMemo(() => ({ | ||
[SOURCE_SYMBOL]: createMutableSource(ref, () => ref.current.v), | ||
[UPDATE_SYMBOL]: <T>(thunk: () => T) => { | ||
priorityRef.current = getCurrentPriorityLevel(); | ||
return runWithPriority(ImmediatePriority, thunk); | ||
}, | ||
}), []); | ||
useIsomorphicLayoutEffect(() => { | ||
ref.current[VALUE_PROP] = value; | ||
runWithPriority(NormalPriority, () => { | ||
ref.current[LISTENERS_PROP].forEach((listener) => listener()); | ||
if (contextValue[SOURCE_SYMBOL]._workInProgressVersionSecondary !== null) { | ||
ref.current.s = value; // update secondary value | ||
} else { | ||
ref.current.p = value; // update primary value | ||
} | ||
ref.current.v += 1; // increment version | ||
runWithPriority(priorityRef.current, () => { | ||
ref.current.l.forEach((listener) => listener()); | ||
}); | ||
priorityRef.current = NormalPriority; | ||
}); | ||
const contextValue = useMemo(() => ({ | ||
[SOURCE_SYMBOL]: createMutableSource(ref, () => ref.current[VALUE_PROP]), | ||
}), []); | ||
return createElement(ProviderOrig, { value: contextValue }, children); | ||
}; | ||
return memo(RefProvider); | ||
return RefProvider; | ||
}; | ||
@@ -81,2 +101,14 @@ | ||
const createDefaultSource = <Value>(defaultValue: Value) => { | ||
const ref: RefValue<Value> = { | ||
current: { | ||
v: -1, // "v" = version | ||
p: defaultValue, // "p" = primary value | ||
s: defaultValue, // "s" = secondary value | ||
l: new Set<() => void>(), // "l" = listeners | ||
}, | ||
}; | ||
return createMutableSource(ref, () => ref.current.v); | ||
}; | ||
/** | ||
@@ -95,4 +127,6 @@ * This creates a special context for selector-enabled `useContext`. | ||
export function createContext<Value>(defaultValue: Value) { | ||
const source = createMutableSource({ current: defaultValue }, () => defaultValue); | ||
const context = createContextOrig({ [SOURCE_SYMBOL]: source }); | ||
const context = createContextOrig<ContextValue<Value>>({ | ||
[SOURCE_SYMBOL]: createDefaultSource(defaultValue), | ||
[UPDATE_SYMBOL]: (thunk) => thunk(), | ||
}); | ||
(context as unknown as Context<Value>).Provider = createProvider(context.Provider); | ||
@@ -104,6 +138,6 @@ delete context.Consumer; // no support for Consumer | ||
const subscribe = ( | ||
ref: MutableRefObject<{ [LISTENERS_PROP]: Set<() => void> }>, | ||
ref: RefValue<unknown>, | ||
callback: () => void, | ||
) => { | ||
const listeners = ref.current[LISTENERS_PROP]; | ||
const listeners = ref.current.l; | ||
listeners.add(callback); | ||
@@ -147,4 +181,7 @@ return () => listeners.delete(callback); | ||
const getSnapshot = useCallback( | ||
(ref: MutableRefObject<{ [VALUE_PROP]: Value }>) => { | ||
const selected = selector(ref.current[VALUE_PROP]); | ||
(ref: RefValue<Value>) => { | ||
const value = source._workInProgressVersionSecondary !== null | ||
? ref.current.s // "s" = secondary value | ||
: ref.current.p; // "p" = primary value | ||
const selected = selector(value); | ||
if (typeof selected === 'function') { | ||
@@ -154,3 +191,3 @@ if (functionMap.has(selected)) { | ||
} | ||
const wrappedFunction = { [FUNCTION_SYNBOL]: selected }; | ||
const wrappedFunction = { [FUNCTION_SYMBOL]: selected }; | ||
functionMap.set(selected, wrappedFunction); | ||
@@ -161,7 +198,7 @@ return wrappedFunction; | ||
}, | ||
[selector], | ||
[selector, source], | ||
); | ||
const snapshot = useMutableSource(source, getSnapshot, subscribe); | ||
if (snapshot && (snapshot as { [FUNCTION_SYNBOL]: unknown })[FUNCTION_SYNBOL]) { | ||
return snapshot[FUNCTION_SYNBOL]; | ||
if (snapshot && (snapshot as { [FUNCTION_SYMBOL]: unknown })[FUNCTION_SYMBOL]) { | ||
return snapshot[FUNCTION_SYMBOL]; | ||
} | ||
@@ -171,20 +208,25 @@ return snapshot; | ||
type AnyCallback = (...args: any) => any; | ||
/** | ||
* A utility function to wrap a callback function with higher priority | ||
* This hook returns an update function that accepts a thunk function | ||
* | ||
* Use this for a callback that will change a value, | ||
* which will be fed into context provider. | ||
* Use this for a function that will change a value. | ||
* | ||
* @example | ||
* import { wrapCallbackWithPriority } from 'use-context-selector'; | ||
* import { useContextUpdate } from 'use-context-selector'; | ||
* | ||
* const wrappedCallback = wrapCallbackWithPriority(callback); | ||
* const update = useContextUpdate(); | ||
* update(() => setState(...)); | ||
*/ | ||
export function wrapCallbackWithPriority<Callback extends AnyCallback>(callback: Callback) { | ||
const callbackWithPriority = (...args: any) => ( | ||
runWithPriority(UserBlockingPriority, () => callback(...args)) | ||
export function useContextUpdate( | ||
context: Context<unknown>, | ||
) { | ||
const { [UPDATE_SYMBOL]: update } = useContextOrig( | ||
context as unknown as ContextOrig<ContextValue<unknown>>, | ||
); | ||
return callbackWithPriority as Callback; | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (!update) { | ||
throw new Error('This useContext requires special context for selector support'); | ||
} | ||
} | ||
return update; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
41094
256
202
4