Skip to content

Commit

Permalink
backport #3889
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Jan 12, 2024
1 parent 6da1922 commit 95ec691
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 37 deletions.
13 changes: 9 additions & 4 deletions src/create-context.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { enqueueRender } from './component';
import { FORCE_UPDATE } from './constants';

let nextContextId = 0;

const providers = new Set();

/** @param {import('./internal').Internal} internal */
export const unsubscribeFromContext = internal => {
export const unsubscribeFromContext = (internal) => {
// if this was a context provider, delete() returns true and we exit:
if (providers.delete(internal)) return;
// ... otherwise, unsubscribe from any contexts:
providers.forEach(p => {
providers.forEach((p) => {
p._component._subs.delete(internal);
});
};
Expand Down Expand Up @@ -39,7 +40,10 @@ export const createContext = (defaultValue, contextId) => {
}
// re-render subscribers in response to value change
else if (props.value !== this._prev) {
this._subs.forEach(enqueueRender);
this._subs.forEach((internal) => {
internal.flags |= FORCE_UPDATE;
enqueueRender(internal);
});
}
this._prev = props.value;

Expand All @@ -53,5 +57,6 @@ export const createContext = (defaultValue, contextId) => {
// of on the component itself. See:
// https://reactjs.org/docs/context.html#contextdisplayname

return (context.Provider._contextRef = context.Consumer.contextType = context);
return (context.Provider._contextRef = context.Consumer.contextType =
context);
};
108 changes: 75 additions & 33 deletions test/browser/createContext.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('createContext', () => {
<Provider value={CONTEXT}>
<div>
<Consumer>
{data => {
{(data) => {
receivedContext = data;
return <Inner {...data} />;
}}
Expand Down Expand Up @@ -94,7 +94,7 @@ describe('createContext', () => {
expect(renders).to.equal(1);
});

it('should preserve provider context through nesting providers', done => {
it('should preserve provider context through nesting providers', (done) => {
const { Provider, Consumer } = createContext();
const CONTEXT = { a: 'a' };
const CHILD_CONTEXT = { b: 'b' };
Expand All @@ -116,12 +116,12 @@ describe('createContext', () => {
render(
<Provider value={CONTEXT}>
<Consumer>
{data => {
{(data) => {
parentContext = data;
return (
<Provider value={CHILD_CONTEXT}>
<Consumer>
{childData => {
{(childData) => {
childContext = childData;
return <Inner {...data} {...childData} />;
}}
Expand Down Expand Up @@ -150,10 +150,8 @@ describe('createContext', () => {
});

it('should preserve provider context between different providers', () => {
const {
Provider: ThemeProvider,
Consumer: ThemeConsumer
} = createContext();
const { Provider: ThemeProvider, Consumer: ThemeConsumer } =
createContext();
const { Provider: DataProvider, Consumer: DataConsumer } = createContext();
const THEME_CONTEXT = { theme: 'black' };
const DATA_CONTEXT = { global: 'a' };
Expand All @@ -177,11 +175,11 @@ describe('createContext', () => {
<ThemeProvider value={THEME_CONTEXT.theme}>
<DataProvider value={DATA_CONTEXT}>
<ThemeConsumer>
{theme => {
{(theme) => {
receivedTheme = theme;
return (
<DataConsumer>
{data => {
{(data) => {
receivedData = data;
return <Inner theme={theme} {...data} />;
}}
Expand Down Expand Up @@ -222,11 +220,11 @@ describe('createContext', () => {
render(
<Provider value={CONTEXT}>
<Consumer>
{data => {
{(data) => {
receivedData = data;
return (
<Consumer>
{childData => {
{(childData) => {
receivedChildData = childData;
return <Inner {...data} {...childData} />;
}}
Expand Down Expand Up @@ -271,7 +269,7 @@ describe('createContext', () => {
<div>
<Provider value={CONTEXT}>
<NoUpdate>
<Consumer>{data => <Inner {...data} />}</Consumer>
<Consumer>{(data) => <Inner {...data} />}</Consumer>
</NoUpdate>
</Provider>
</div>,
Expand All @@ -284,7 +282,7 @@ describe('createContext', () => {
<div>
<Provider value={CONTEXT}>
<NoUpdate>
<Consumer>{data => <Inner {...data} />}</Consumer>
<Consumer>{(data) => <Inner {...data} />}</Consumer>
</NoUpdate>
</Provider>
</div>,
Expand Down Expand Up @@ -333,7 +331,7 @@ describe('createContext', () => {
return (
<div>
<Consumer>
{data => {
{(data) => {
receivedContext = data;
return <Consumed {...data} />;
}}
Expand All @@ -360,7 +358,7 @@ describe('createContext', () => {
);
});

it('should propagates through shouldComponentUpdate false', done => {
it('should propagates through shouldComponentUpdate false', (done) => {
const { Provider, Consumer } = createContext();
const CONTEXT = { a: 'a' };
const UPDATED_CONTEXT = { a: 'b' };
Expand Down Expand Up @@ -401,7 +399,7 @@ describe('createContext', () => {
render() {
return (
<div>
<Consumer>{data => <Consumed {...data} />}</Consumer>
<Consumer>{(data) => <Consumed {...data} />}</Consumer>
</div>
);
}
Expand Down Expand Up @@ -473,14 +471,14 @@ describe('createContext', () => {
<Provider value={CONTEXT}>
<Provider value={NESTED_CONTEXT}>
<Consumer>
{data => {
{(data) => {
receivedNestedData = data;
return <Nested {...data} />;
}}
</Consumer>
</Provider>
<Consumer>
{data => {
{(data) => {
receivedData = data;
return <Inner {...data} />;
}}
Expand Down Expand Up @@ -525,7 +523,7 @@ describe('createContext', () => {
render(
<Provider value={CONTEXT}>
<NoUpdate>
<Consumer>{data => <Inner {...data} />}</Consumer>
<Consumer>{(data) => <Inner {...data} />}</Consumer>
</NoUpdate>
</Provider>,
scratch
Expand All @@ -534,7 +532,7 @@ describe('createContext', () => {
render(
<Provider value={CONTEXT}>
<NoUpdate>
<Consumer>{data => <Inner {...data} />}</Consumer>
<Consumer>{(data) => <Inner {...data} />}</Consumer>
</NoUpdate>
</Provider>,
scratch
Expand All @@ -550,17 +548,17 @@ describe('createContext', () => {
render(
<Provider value={{ i: 2 }}>
<NoUpdate>
<Consumer>{data => <Inner {...data} />}</Consumer>
<Consumer>{(data) => <Inner {...data} />}</Consumer>
</NoUpdate>
</Provider>,
scratch
);
});

// Rendered three times, should call 'Consumer' render two times
expect(
Inner.prototype.render
).to.have.been.calledTwice.and.calledWithMatch({ i: 2 });
expect(Inner.prototype.render).to.have.been.calledTwice.and.calledWithMatch(
{ i: 2 }
);
expect(scratch.innerHTML).to.equal('<div>2</div>');
});

Expand Down Expand Up @@ -625,15 +623,15 @@ describe('createContext', () => {
act(() => {
render(
<Provider value={CONTEXT}>
<Consumer>{data => <Inner {...data} />}</Consumer>
<Consumer>{(data) => <Inner {...data} />}</Consumer>
</Provider>,
scratch
);

// Not calling re-render since it's gonna get called with the same Consumer function
render(
<Provider value={CONTEXT}>
<Consumer>{data => <Inner {...data} />}</Consumer>
<Consumer>{(data) => <Inner {...data} />}</Consumer>
</Provider>,
scratch
);
Expand All @@ -658,15 +656,15 @@ describe('createContext', () => {
super();

this.state = { value: 0, show: true };
changeValue = value => this.setState({ value });
changeValue = (value) => this.setState({ value });
toggleConsumer = () => this.setState(({ show }) => ({ show: !show }));
}
render(props, state) {
return (
<Provider value={state.value}>
<div>
{state.show ? (
<Consumer>{data => <Inner value={data} />}</Consumer>
<Consumer>{(data) => <Inner value={data} />}</Consumer>
) : null}
</div>
</Provider>
Expand Down Expand Up @@ -828,7 +826,7 @@ describe('createContext', () => {
}

updateStore() {
this.setState(state => ({ id: state.id + 1 }));
this.setState((state) => ({ id: state.id + 1 }));
}

render() {
Expand All @@ -846,12 +844,12 @@ describe('createContext', () => {
}

render() {
return <Store.Consumer>{id => <Parent key={id} />}</Store.Consumer>;
return <Store.Consumer>{(id) => <Parent key={id} />}</Store.Consumer>;
}
}

function Parent(props) {
return <Store.Consumer>{id => <Child id={id} />}</Store.Consumer>;
return <Store.Consumer>{(id) => <Child id={id} />}</Store.Consumer>;
}

class Child extends Component {
Expand Down Expand Up @@ -899,7 +897,7 @@ describe('createContext', () => {
}

render() {
return <context.Consumer>{v => <p>{v.state}</p>}</context.Consumer>;
return <context.Consumer>{(v) => <p>{v.state}</p>}</context.Consumer>;
}
}

Expand Down Expand Up @@ -930,4 +928,48 @@ describe('createContext', () => {
rerender();
expect(scratch.innerHTML).to.equal('<p>hi</p>');
});

it('should not call sCU on context update', () => {
const Ctx = createContext('foo');

/** @type {(s: string) => void} */
let update;
class App extends Component {
constructor(props) {
super(props);
this.state = { foo: 'foo' };
update = (v) => this.setState({ foo: v });
}
render() {
return (
<Ctx.Provider value={this.state.foo}>
<Child />
</Ctx.Provider>
);
}
}

const spy = sinon.spy();

class Child extends Component {
static contextType = Ctx;

shouldComponentUpdate() {
spy();
return false;
}

render() {
return <p>{this.context}</p>;
}
}

render(<App />, scratch);
expect(scratch.textContent).to.equal('foo');

update('bar');
rerender();
expect(scratch.textContent).to.equal('bar');
expect(spy).not.to.be.called;
});
});

0 comments on commit 95ec691

Please sign in to comment.