Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
487f4bf2ee | ||
|
|
297165f1e1 | ||
|
|
cd5b8950a0 | ||
|
|
f00be84b81 | ||
|
|
e0c2c56dfd | ||
|
|
aa8736a3a9 | ||
|
|
d9fa20d52f |
@@ -6,6 +6,15 @@
|
||||
</summary>
|
||||
</details>
|
||||
|
||||
## 16.8.6 (March 27, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix an incorrect bailout in `useReducer()`. ([@acdlite](https://github.com/acdlite) in [#15124](https://github.com/facebook/react/pull/15124))
|
||||
* Fix iframe warnings in Safari DevTools. ([@renanvalentin](https://github.com/renanvalentin) in [#15099](https://github.com/facebook/react/pull/15099))
|
||||
* Warn if `contextType` is set to `Context.Consumer` instead of `Context`. ([@aweary](https://github.com/aweary) in [#14831](https://github.com/facebook/react/pull/14831))
|
||||
* Warn if `contextType` is set to invalid values. ([@gaearon](https://github.com/gaearon) in [#15142](https://github.com/facebook/react/pull/15142))
|
||||
|
||||
## 16.8.5 (March 22, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.8.5",
|
||||
"version": "16.8.6",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jest-react",
|
||||
"version": "0.6.5",
|
||||
"version": "0.6.6",
|
||||
"description": "Jest matchers and utilities for testing React components.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-art",
|
||||
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
|
||||
"version": "16.8.5",
|
||||
"version": "16.8.6",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -27,7 +27,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.5"
|
||||
"scheduler": "^0.13.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "16.8.5",
|
||||
"version": "16.8.6",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.5"
|
||||
"scheduler": "^0.13.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -904,4 +904,125 @@ describe('ReactDOMServer', () => {
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn if an invalid contextType is defined', () => {
|
||||
const Context = React.createContext();
|
||||
|
||||
class ComponentA extends React.Component {
|
||||
// It should warn for both Context.Consumer and Context.Provider
|
||||
static contextType = Context.Consumer;
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
class ComponentB extends React.Component {
|
||||
static contextType = Context.Provider;
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<ComponentA />);
|
||||
}).toWarnDev(
|
||||
'Warning: ComponentA defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Consumer instead?',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// Warnings should be deduped by component type
|
||||
ReactDOMServer.renderToString(<ComponentA />);
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<ComponentB />);
|
||||
}).toWarnDev(
|
||||
'Warning: ComponentB defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Provider instead?',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should not warn when class contextType is null', () => {
|
||||
class Foo extends React.Component {
|
||||
static contextType = null; // Handy for conditional declaration
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
});
|
||||
|
||||
it('should warn when class contextType is undefined', () => {
|
||||
class Foo extends React.Component {
|
||||
// This commonly happens with circular deps
|
||||
// https://github.com/facebook/react/issues/13969
|
||||
static contextType = undefined;
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
}).toWarnDev(
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to undefined. ' +
|
||||
'This can be caused by a typo or by mixing up named and default imports. ' +
|
||||
'This can also happen due to a circular dependency, ' +
|
||||
'so try moving the createContext() call to a separate file.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when class contextType is an object', () => {
|
||||
class Foo extends React.Component {
|
||||
// Can happen due to a typo
|
||||
static contextType = {
|
||||
x: 42,
|
||||
y: 'hello',
|
||||
};
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'hello' of undefined");
|
||||
}).toWarnDev(
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to an object with keys {x, y}.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when class contextType is a primitive', () => {
|
||||
class Foo extends React.Component {
|
||||
static contextType = 'foo';
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
}).toWarnDev(
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to a string.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,15 +40,29 @@ function isInDocument(node) {
|
||||
);
|
||||
}
|
||||
|
||||
function isSameOriginFrame(iframe) {
|
||||
try {
|
||||
// Accessing the contentDocument of a HTMLIframeElement can cause the browser
|
||||
// to throw, e.g. if it has a cross-origin src attribute.
|
||||
// Safari will show an error in the console when the access results in "Blocked a frame with origin". e.g:
|
||||
// iframe.contentDocument.defaultView;
|
||||
// A safety way is to access one of the cross origin properties: Window or Location
|
||||
// Which might result in "SecurityError" DOM Exception and it is compatible to Safari.
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#integration-with-idl
|
||||
|
||||
return typeof iframe.contentWindow.location.href === 'string';
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getActiveElementDeep() {
|
||||
let win = window;
|
||||
let element = getActiveElement();
|
||||
while (element instanceof win.HTMLIFrameElement) {
|
||||
// Accessing the contentDocument of a HTMLIframeElement can cause the browser
|
||||
// to throw, e.g. if it has a cross-origin src attribute
|
||||
try {
|
||||
win = element.contentDocument.defaultView;
|
||||
} catch (e) {
|
||||
if (isSameOriginFrame(element)) {
|
||||
win = element.contentWindow;
|
||||
} else {
|
||||
return element;
|
||||
}
|
||||
element = getActiveElement(win.document);
|
||||
|
||||
@@ -10,19 +10,19 @@
|
||||
import type {ThreadID} from './ReactThreadIDAllocator';
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
|
||||
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
|
||||
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
import checkPropTypes from 'prop-types/checkPropTypes';
|
||||
|
||||
let ReactDebugCurrentFrame;
|
||||
let didWarnAboutInvalidateContextType;
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
||||
didWarnAboutInvalidateContextType = new Set();
|
||||
}
|
||||
|
||||
const didWarnAboutInvalidateContextType = {};
|
||||
|
||||
export const emptyObject = {};
|
||||
if (__DEV__) {
|
||||
Object.freeze(emptyObject);
|
||||
@@ -75,22 +75,49 @@ export function processContext(
|
||||
threadID: ThreadID,
|
||||
) {
|
||||
const contextType = type.contextType;
|
||||
if (typeof contextType === 'object' && contextType !== null) {
|
||||
if (__DEV__) {
|
||||
if (contextType.$$typeof !== REACT_CONTEXT_TYPE) {
|
||||
let name = getComponentName(type) || 'Component';
|
||||
if (!didWarnAboutInvalidateContextType[name]) {
|
||||
didWarnAboutInvalidateContextType[name] = true;
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'%s defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Provider instead?',
|
||||
name,
|
||||
);
|
||||
if (__DEV__) {
|
||||
if ('contextType' in (type: any)) {
|
||||
let isValid =
|
||||
// Allow null for conditional declaration
|
||||
contextType === null ||
|
||||
(contextType !== undefined &&
|
||||
contextType.$$typeof === REACT_CONTEXT_TYPE &&
|
||||
contextType._context === undefined); // Not a <Context.Consumer>
|
||||
|
||||
if (!isValid && !didWarnAboutInvalidateContextType.has(type)) {
|
||||
didWarnAboutInvalidateContextType.add(type);
|
||||
|
||||
let addendum = '';
|
||||
if (contextType === undefined) {
|
||||
addendum =
|
||||
' However, it is set to undefined. ' +
|
||||
'This can be caused by a typo or by mixing up named and default imports. ' +
|
||||
'This can also happen due to a circular dependency, so ' +
|
||||
'try moving the createContext() call to a separate file.';
|
||||
} else if (typeof contextType !== 'object') {
|
||||
addendum = ' However, it is set to a ' + typeof contextType + '.';
|
||||
} else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
|
||||
addendum = ' Did you accidentally pass the Context.Provider instead?';
|
||||
} else if (contextType._context !== undefined) {
|
||||
// <Context.Consumer>
|
||||
addendum = ' Did you accidentally pass the Context.Consumer instead?';
|
||||
} else {
|
||||
addendum =
|
||||
' However, it is set to an object with keys {' +
|
||||
Object.keys(contextType).join(', ') +
|
||||
'}.';
|
||||
}
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'%s defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext().%s',
|
||||
getComponentName(type) || 'Component',
|
||||
addendum,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof contextType === 'object' && contextType !== null) {
|
||||
validateContextBounds(contextType, threadID);
|
||||
return contextType[threadID];
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-is",
|
||||
"version": "16.8.5",
|
||||
"version": "16.8.6",
|
||||
"description": "Brand checking of React Elements.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-reconciler",
|
||||
"description": "React package for creating custom renderers.",
|
||||
"version": "0.20.3",
|
||||
"version": "0.20.4",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
@@ -33,7 +33,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.5"
|
||||
"scheduler": "^0.13.6"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
|
||||
@@ -24,7 +24,7 @@ import shallowEqual from 'shared/shallowEqual';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
|
||||
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
|
||||
import {resolveDefaultProps} from './ReactFiberLazyComponent';
|
||||
@@ -513,23 +513,51 @@ function constructClassInstance(
|
||||
let unmaskedContext = emptyContextObject;
|
||||
let context = null;
|
||||
const contextType = ctor.contextType;
|
||||
if (typeof contextType === 'object' && contextType !== null) {
|
||||
if (__DEV__) {
|
||||
if (
|
||||
contextType.$$typeof !== REACT_CONTEXT_TYPE &&
|
||||
!didWarnAboutInvalidateContextType.has(ctor)
|
||||
) {
|
||||
|
||||
if (__DEV__) {
|
||||
if ('contextType' in ctor) {
|
||||
let isValid =
|
||||
// Allow null for conditional declaration
|
||||
contextType === null ||
|
||||
(contextType !== undefined &&
|
||||
contextType.$$typeof === REACT_CONTEXT_TYPE &&
|
||||
contextType._context === undefined); // Not a <Context.Consumer>
|
||||
|
||||
if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
|
||||
didWarnAboutInvalidateContextType.add(ctor);
|
||||
|
||||
let addendum = '';
|
||||
if (contextType === undefined) {
|
||||
addendum =
|
||||
' However, it is set to undefined. ' +
|
||||
'This can be caused by a typo or by mixing up named and default imports. ' +
|
||||
'This can also happen due to a circular dependency, so ' +
|
||||
'try moving the createContext() call to a separate file.';
|
||||
} else if (typeof contextType !== 'object') {
|
||||
addendum = ' However, it is set to a ' + typeof contextType + '.';
|
||||
} else if (contextType.$$typeof === REACT_PROVIDER_TYPE) {
|
||||
addendum = ' Did you accidentally pass the Context.Provider instead?';
|
||||
} else if (contextType._context !== undefined) {
|
||||
// <Context.Consumer>
|
||||
addendum = ' Did you accidentally pass the Context.Consumer instead?';
|
||||
} else {
|
||||
addendum =
|
||||
' However, it is set to an object with keys {' +
|
||||
Object.keys(contextType).join(', ') +
|
||||
'}.';
|
||||
}
|
||||
warningWithoutStack(
|
||||
false,
|
||||
'%s defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Provider instead?',
|
||||
'contextType should point to the Context object returned by React.createContext().%s',
|
||||
getComponentName(ctor) || 'Component',
|
||||
addendum,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof contextType === 'object' && contextType !== null) {
|
||||
context = readContext((contextType: any));
|
||||
} else {
|
||||
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
|
||||
|
||||
30
packages/react-reconciler/src/ReactFiberHooks.js
vendored
30
packages/react-reconciler/src/ReactFiberHooks.js
vendored
@@ -89,8 +89,8 @@ type Update<S, A> = {
|
||||
type UpdateQueue<S, A> = {
|
||||
last: Update<S, A> | null,
|
||||
dispatch: (A => mixed) | null,
|
||||
eagerReducer: ((S, A) => S) | null,
|
||||
eagerState: S | null,
|
||||
lastRenderedReducer: ((S, A) => S) | null,
|
||||
lastRenderedState: S | null,
|
||||
};
|
||||
|
||||
export type HookType =
|
||||
@@ -591,8 +591,8 @@ function mountReducer<S, I, A>(
|
||||
const queue = (hook.queue = {
|
||||
last: null,
|
||||
dispatch: null,
|
||||
eagerReducer: reducer,
|
||||
eagerState: (initialState: any),
|
||||
lastRenderedReducer: reducer,
|
||||
lastRenderedState: (initialState: any),
|
||||
});
|
||||
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
|
||||
null,
|
||||
@@ -615,6 +615,8 @@ function updateReducer<S, I, A>(
|
||||
'Should have a queue. This is likely a bug in React. Please file an issue.',
|
||||
);
|
||||
|
||||
queue.lastRenderedReducer = reducer;
|
||||
|
||||
if (numberOfReRenders > 0) {
|
||||
// This is a re-render. Apply the new render phase updates to the previous
|
||||
// work-in-progress hook.
|
||||
@@ -650,8 +652,7 @@ function updateReducer<S, I, A>(
|
||||
hook.baseState = newState;
|
||||
}
|
||||
|
||||
queue.eagerReducer = reducer;
|
||||
queue.eagerState = newState;
|
||||
queue.lastRenderedState = newState;
|
||||
|
||||
return [newState, dispatch];
|
||||
}
|
||||
@@ -730,8 +731,7 @@ function updateReducer<S, I, A>(
|
||||
hook.baseUpdate = newBaseUpdate;
|
||||
hook.baseState = newBaseState;
|
||||
|
||||
queue.eagerReducer = reducer;
|
||||
queue.eagerState = newState;
|
||||
queue.lastRenderedState = newState;
|
||||
}
|
||||
|
||||
const dispatch: Dispatch<A> = (queue.dispatch: any);
|
||||
@@ -749,8 +749,8 @@ function mountState<S>(
|
||||
const queue = (hook.queue = {
|
||||
last: null,
|
||||
dispatch: null,
|
||||
eagerReducer: basicStateReducer,
|
||||
eagerState: (initialState: any),
|
||||
lastRenderedReducer: basicStateReducer,
|
||||
lastRenderedState: (initialState: any),
|
||||
});
|
||||
const dispatch: Dispatch<
|
||||
BasicStateAction<S>,
|
||||
@@ -1129,21 +1129,21 @@ function dispatchAction<S, A>(
|
||||
// The queue is currently empty, which means we can eagerly compute the
|
||||
// next state before entering the render phase. If the new state is the
|
||||
// same as the current state, we may be able to bail out entirely.
|
||||
const eagerReducer = queue.eagerReducer;
|
||||
if (eagerReducer !== null) {
|
||||
const lastRenderedReducer = queue.lastRenderedReducer;
|
||||
if (lastRenderedReducer !== null) {
|
||||
let prevDispatcher;
|
||||
if (__DEV__) {
|
||||
prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
}
|
||||
try {
|
||||
const currentState: S = (queue.eagerState: any);
|
||||
const eagerState = eagerReducer(currentState, action);
|
||||
const currentState: S = (queue.lastRenderedState: any);
|
||||
const eagerState = lastRenderedReducer(currentState, action);
|
||||
// Stash the eagerly computed state, and the reducer used to compute
|
||||
// it, on the update object. If the reducer hasn't changed by the
|
||||
// time we enter the render phase, then the eager state can be used
|
||||
// without calling the reducer again.
|
||||
update.eagerReducer = eagerReducer;
|
||||
update.eagerReducer = lastRenderedReducer;
|
||||
update.eagerState = eagerState;
|
||||
if (is(eagerState, currentState)) {
|
||||
// Fast path. We can bail out without scheduling React to re-render.
|
||||
|
||||
@@ -1871,4 +1871,89 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
// );
|
||||
});
|
||||
});
|
||||
|
||||
it('eager bailout optimization should always compare to latest rendered reducer', () => {
|
||||
// Edge case based on a bug report
|
||||
let setCounter;
|
||||
function App() {
|
||||
const [counter, _setCounter] = useState(1);
|
||||
setCounter = _setCounter;
|
||||
return <Component count={counter} />;
|
||||
}
|
||||
|
||||
function Component({count}) {
|
||||
const [state, dispatch] = useReducer(() => {
|
||||
// This reducer closes over a value from props. If the reducer is not
|
||||
// properly updated, the eager reducer will compare to an old value
|
||||
// and bail out incorrectly.
|
||||
ReactNoop.yield('Reducer: ' + count);
|
||||
return count;
|
||||
}, -1);
|
||||
useEffect(
|
||||
() => {
|
||||
ReactNoop.yield('Effect: ' + count);
|
||||
dispatch();
|
||||
},
|
||||
[count],
|
||||
);
|
||||
ReactNoop.yield('Render: ' + state);
|
||||
return <span prop={count} />;
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
expect(ReactNoop.flush()).toEqual(['Render: -1']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(ReactNoop.flush()).toEqual([
|
||||
'Effect: 1',
|
||||
'Reducer: 1',
|
||||
'Reducer: 1',
|
||||
'Render: 1',
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1)]);
|
||||
|
||||
act(() => {
|
||||
setCounter(2);
|
||||
});
|
||||
expect(ReactNoop.flush()).toEqual([
|
||||
'Render: 1',
|
||||
'Effect: 2',
|
||||
'Reducer: 2',
|
||||
'Reducer: 2',
|
||||
'Render: 2',
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(2)]);
|
||||
});
|
||||
|
||||
it('should update latest rendered reducer when a preceding state receives a render phase update', () => {
|
||||
// Similar to previous test, except using a preceding render phase update
|
||||
// instead of new props.
|
||||
let dispatch;
|
||||
function App() {
|
||||
const [step, setStep] = useState(0);
|
||||
const [shadow, _dispatch] = useReducer(() => step, step);
|
||||
dispatch = _dispatch;
|
||||
|
||||
if (step < 5) {
|
||||
setStep(step + 1);
|
||||
}
|
||||
|
||||
ReactNoop.yield(`Step: ${step}, Shadow: ${shadow}`);
|
||||
return <span prop={shadow} />;
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
expect(ReactNoop.flush()).toEqual([
|
||||
'Step: 0, Shadow: 0',
|
||||
'Step: 1, Shadow: 0',
|
||||
'Step: 2, Shadow: 0',
|
||||
'Step: 3, Shadow: 0',
|
||||
'Step: 4, Shadow: 0',
|
||||
'Step: 5, Shadow: 0',
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(0)]);
|
||||
|
||||
act(() => dispatch());
|
||||
expect(ReactNoop.flush()).toEqual(['Step: 5, Shadow: 5']);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(5)]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-test-renderer",
|
||||
"version": "16.8.5",
|
||||
"version": "16.8.6",
|
||||
"description": "React package for snapshot testing.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -21,8 +21,8 @@
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.8.5",
|
||||
"scheduler": "^0.13.5"
|
||||
"react-is": "^16.8.6",
|
||||
"scheduler": "^0.13.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
"version": "16.8.5",
|
||||
"version": "16.8.6",
|
||||
"homepage": "https://reactjs.org/",
|
||||
"bugs": "https://github.com/facebook/react/issues",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.5"
|
||||
"scheduler": "^0.13.6"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
|
||||
@@ -541,9 +541,10 @@ describe('ReactContextValidator', () => {
|
||||
|
||||
it('should warn if an invalid contextType is defined', () => {
|
||||
const Context = React.createContext();
|
||||
|
||||
// This tests that both Context.Consumer and Context.Provider
|
||||
// warn about invalid contextType.
|
||||
class ComponentA extends React.Component {
|
||||
static contextType = Context.Provider;
|
||||
static contextType = Context.Consumer;
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
@@ -560,7 +561,7 @@ describe('ReactContextValidator', () => {
|
||||
}).toWarnDev(
|
||||
'Warning: ComponentA defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Provider instead?',
|
||||
'Did you accidentally pass the Context.Consumer instead?',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
@@ -577,6 +578,87 @@ describe('ReactContextValidator', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not warn when class contextType is null', () => {
|
||||
class Foo extends React.Component {
|
||||
static contextType = null; // Handy for conditional declaration
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
});
|
||||
|
||||
it('should warn when class contextType is undefined', () => {
|
||||
class Foo extends React.Component {
|
||||
// This commonly happens with circular deps
|
||||
// https://github.com/facebook/react/issues/13969
|
||||
static contextType = undefined;
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
}).toWarnDev(
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to undefined. ' +
|
||||
'This can be caused by a typo or by mixing up named and default imports. ' +
|
||||
'This can also happen due to a circular dependency, ' +
|
||||
'so try moving the createContext() call to a separate file.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when class contextType is an object', () => {
|
||||
class Foo extends React.Component {
|
||||
// Can happen due to a typo
|
||||
static contextType = {
|
||||
x: 42,
|
||||
y: 'hello',
|
||||
};
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(<Foo />);
|
||||
}).toThrow("Cannot read property 'hello' of undefined");
|
||||
}).toWarnDev(
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to an object with keys {x, y}.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when class contextType is a primitive', () => {
|
||||
class Foo extends React.Component {
|
||||
static contextType = 'foo';
|
||||
render() {
|
||||
return this.context.hello.world;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
}).toWarnDev(
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to a string.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn if you define contextType on a function component', () => {
|
||||
const Context = React.createContext();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "scheduler",
|
||||
"version": "0.13.5",
|
||||
"version": "0.13.6",
|
||||
"description": "Cooperative scheduler for the browser environment.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
'use strict';
|
||||
|
||||
// TODO: this is special because it gets imported during build.
|
||||
module.exports = '16.8.5';
|
||||
module.exports = '16.8.6';
|
||||
|
||||
Reference in New Issue
Block a user