diff --git a/compat/src/index.d.ts b/compat/src/index.d.ts index 23de2d5ed7..f9aa95ca95 100644 --- a/compat/src/index.d.ts +++ b/compat/src/index.d.ts @@ -50,21 +50,15 @@ declare namespace React { ): T; // Preact Defaults - export interface Context extends preact.Provider { - Consumer: preact.Consumer; - Provider: preact.Provider; - displayName?: string; - } - export function createContext(defaultValue: T): Context; - export type ContextType> = C extends Context - ? T - : never; + export import Context = preact.Context; + export import ContextType = preact.ContextType; export import RefObject = preact.RefObject; export import Component = preact.Component; export import FunctionComponent = preact.FunctionComponent; export import ComponentType = preact.ComponentType; export import ComponentClass = preact.ComponentClass; export import FC = preact.FunctionComponent; + export import createContext = preact.createContext; export import Ref = preact.Ref; export import createRef = preact.createRef; export import Fragment = preact.Fragment; diff --git a/compat/src/render.js b/compat/src/render.js index 8f7d18b338..f18cbd896b 100644 --- a/compat/src/render.js +++ b/compat/src/render.js @@ -242,16 +242,6 @@ function handleDomVNode(vnode) { let oldVNodeHook = options.vnode; options.vnode = vnode => { - // @ts-expect-error type can't be null, however TS is confused - if ( - vnode.type != null && - typeof vnode.type === 'object' && - 'Provider' in vnode.type - ) { - // @ts-expect-error - vnode.type = vnode.type.Provider; - } - // only normalize props on Element nodes if (typeof vnode.type === 'string') { handleDomVNode(vnode); diff --git a/hooks/test/browser/useContext.test.js b/hooks/test/browser/useContext.test.js index 67b7851406..6a47b107d8 100644 --- a/hooks/test/browser/useContext.test.js +++ b/hooks/test/browser/useContext.test.js @@ -206,6 +206,38 @@ describe('useContext', () => { expect(values).to.deep.equal([13, 42, 69]); }); + it('should only subscribe a component once (non-provider)', () => { + const values = []; + const Context = createContext(13); + let provider, subSpy; + + function Comp() { + const value = useContext(Context); + values.push(value); + return null; + } + + render(, scratch); + + render( + (provider = p)} value={42}> + + , + scratch + ); + subSpy = sinon.spy(provider, 'sub'); + + render( + + + , + scratch + ); + expect(subSpy).to.not.have.been.called; + + expect(values).to.deep.equal([13, 42, 69]); + }); + it('should maintain context', done => { const context = createContext(null); const { Provider } = context; diff --git a/mangle.json b/mangle.json index 1d009545cb..a0a2a5dbe9 100644 --- a/mangle.json +++ b/mangle.json @@ -60,7 +60,7 @@ "$_processingException": "__", "$_globalContext": "__n", "$_context": "c", - "$_defaultValue": "__", + "$_defaultValue": "__d", "$_id": "__c", "$_contextRef": "__", "$_parentDom": "__P", diff --git a/src/create-context.js b/src/create-context.js index 1bb9868f6d..d9ec9904e8 100644 --- a/src/create-context.js +++ b/src/create-context.js @@ -2,64 +2,58 @@ import { enqueueRender } from './component'; export let i = 0; -export function createContext(defaultValue, contextId) { - contextId = '__cC' + i++; - - const context = { - _id: contextId, - _defaultValue: defaultValue, - /** @type {import('./internal').FunctionComponent} */ - Consumer(props, contextValue) { - // return props.children( - // context[contextId] ? context[contextId].props.value : defaultValue - // ); - return props.children(contextValue); - }, - /** @type {import('./internal').FunctionComponent} */ - Provider(props) { - if (!this.getChildContext) { - /** @type {Set | null} */ - let subs = new Set(); - let ctx = {}; - ctx[contextId] = this; - - this.getChildContext = () => ctx; - - this.componentWillUnmount = () => { - subs = null; - }; - - this.shouldComponentUpdate = function (_props) { - if (this.props.value !== _props.value) { - subs.forEach(c => { - c._force = true; - enqueueRender(c); - }); +export function createContext(defaultValue) { + /** @type {import('./internal').FunctionComponent} */ + function Context(props) { + if (!this.getChildContext) { + /** @type {Set | null} */ + let subs = new Set(); + let ctx = {}; + ctx[Context._id] = this; + + this.getChildContext = () => ctx; + + this.componentWillUnmount = () => { + subs = null; + }; + + this.shouldComponentUpdate = function (_props) { + if (this.props.value !== _props.value) { + subs.forEach(c => { + c._force = true; + enqueueRender(c); + }); + } + }; + + this.sub = c => { + subs.add(c); + let old = c.componentWillUnmount; + c.componentWillUnmount = () => { + if (subs) { + subs.delete(c); } + if (old) old.call(c); }; + }; + } - this.sub = c => { - subs.add(c); - let old = c.componentWillUnmount; - c.componentWillUnmount = () => { - if (subs) { - subs.delete(c); - } - if (old) old.call(c); - }; - }; - } + return props.children; + } - return props.children; - } + Context._id = '__cC' + i++; + Context._defaultValue = defaultValue; + + /** @type {import('./internal').FunctionComponent} */ + Context.Consumer = (props, contextValue) => { + return props.children(contextValue); }; - // Devtools needs access to the context object when it - // encounters a Provider. This is necessary to support - // setting `displayName` on the context object instead - // of on the component itself. See: - // https://reactjs.org/docs/context.html#contextdisplayname + // we could also get rid of _contextRef entirely + Context.Provider = + Context._contextRef = + Context.Consumer.contextType = + Context; - return (context.Provider._contextRef = context.Consumer.contextType = - context); + return Context; } diff --git a/src/index.d.ts b/src/index.d.ts index 279af93ab2..8ab4570dc5 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -388,11 +388,12 @@ export type ContextType> = C extends Context ? T : never; -export interface Context { - Consumer: Consumer; - Provider: Provider; +export interface Context extends preact.Provider { + Consumer: preact.Consumer; + Provider: preact.Provider; displayName?: string; } + export interface PreactContext extends Context {} export function createContext(defaultValue: T): Context;