Skip to content

Commit

Permalink
Implement in core
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Dec 31, 2024
1 parent 4163c13 commit 8d7f867
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 76 deletions.
12 changes: 3 additions & 9 deletions compat/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,15 @@ declare namespace React {
): T;

// Preact Defaults
export interface Context<T> extends preact.Provider<T> {
Consumer: preact.Consumer<T>;
Provider: preact.Provider<T>;
displayName?: string;
}
export function createContext<T>(defaultValue: T): Context<T>;
export type ContextType<C extends Context<any>> = C extends Context<infer T>
? 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;
Expand Down
10 changes: 0 additions & 10 deletions compat/src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
32 changes: 32 additions & 0 deletions hooks/test/browser/useContext.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(<Comp />, scratch);

render(
<Context ref={p => (provider = p)} value={42}>
<Comp />
</Context>,
scratch
);
subSpy = sinon.spy(provider, 'sub');

render(
<Context value={69}>
<Comp />
</Context>,
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;
Expand Down
2 changes: 1 addition & 1 deletion mangle.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"$_processingException": "__",
"$_globalContext": "__n",
"$_context": "c",
"$_defaultValue": "__",
"$_defaultValue": "__d",
"$_id": "__c",
"$_contextRef": "__",
"$_parentDom": "__P",
Expand Down
100 changes: 47 additions & 53 deletions src/create-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<import('./internal').Component> | 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<import('./internal').Component> | 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) {

Check failure on line 21 in src/create-context.js

View workflow job for this annotation

GitHub Actions / Build & Test / Build & Test

Property 'props' does not exist on type 'Context'.
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;
}
7 changes: 4 additions & 3 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,11 +388,12 @@ export type ContextType<C extends Context<any>> = C extends Context<infer T>
? T
: never;

export interface Context<T> {
Consumer: Consumer<T>;
Provider: Provider<T>;
export interface Context<T> extends preact.Provider<T> {
Consumer: preact.Consumer<T>;
Provider: preact.Provider<T>;
displayName?: string;
}

export interface PreactContext<T> extends Context<T> {}

export function createContext<T>(defaultValue: T): Context<T>;

0 comments on commit 8d7f867

Please sign in to comment.