Skip to content

Commit c00a920

Browse files
author
Brian Vaughn
committed
Merge branch 'master' into devtools-v4-merge
2 parents 79bda69 + 0da7bd0 commit c00a920

File tree

69 files changed

+1989
-696
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+1989
-696
lines changed

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ module.exports = {
142142
],
143143

144144
globals: {
145+
SharedArrayBuffer: true,
146+
145147
spyOnDev: true,
146148
spyOnDevAndProd: true,
147149
spyOnProd: true,

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
* Warn in Strict Mode if effects are scheduled outside an `act()` call. ([@threepointone](https://github.com/threepointone) in [#15763](https://github.com/facebook/react/pull/15763) and [#16041](https://github.com/facebook/react/pull/16041))
4242
* Warn when using `act` from the wrong renderer. ([@threepointone](https://github.com/threepointone) in [#15756](https://github.com/facebook/react/pull/15756))
4343

44+
### ESLint Plugin: React Hooks
45+
46+
* Report Hook calls at the top level as a violation. ([gaearon](https://github.com/gaearon) in [#16455](https://github.com/facebook/react/pull/16455))
47+
4448
## 16.8.6 (March 27, 2019)
4549

4650
### React DOM

packages/eslint-plugin-react-hooks/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "eslint-plugin-react-hooks",
33
"description": "ESLint rules for React Hooks",
4-
"version": "1.7.0",
4+
"version": "2.0.1",
55
"repository": {
66
"type": "git",
77
"url": "https://github.com/facebook/react.git",

packages/eslint-plugin-react-hooks/src/RulesOfHooks.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,7 @@ export default {
432432
'React Hook function.';
433433
context.report({node: hook, message});
434434
} else if (codePathNode.type === 'Program') {
435-
// We could warn here but there are false positives related
436-
// configuring libraries like `history`.
435+
// These are dangerous if you have inline requires enabled.
437436
const message =
438437
`React Hook "${context.getSource(hook)}" cannot be called ` +
439438
'at the top level. React Hooks must be called in a ' +

packages/react-art/src/ReactARTHostConfig.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,6 @@ export function mountResponderInstance(
432432
props: Object,
433433
state: Object,
434434
instance: Object,
435-
rootContainerInstance: Object,
436435
) {
437436
throw new Error('Not yet implemented.');
438437
}

packages/react-devtools/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
<!-- Upcoming changes go here -->
1010
</details>
1111

12+
## 4.0.6 (August 26, 2019)
13+
#### Bug fixes
14+
* Remove ⚛️ emoji prefix from Firefox extension tab labels
15+
* Standalone polyfills `Symbol` usage
16+
1217
## 4.0.5 (August 19, 2019)
1318
#### Bug fixes
1419
* Props, state, and context values are alpha sorted.

packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe('ReactDOMServerPartialHydration', () => {
2525
ReactFeatureFlags = require('shared/ReactFeatureFlags');
2626
ReactFeatureFlags.enableSuspenseServerRenderer = true;
2727
ReactFeatureFlags.enableSuspenseCallback = true;
28+
ReactFeatureFlags.enableFlareAPI = true;
2829

2930
React = require('react');
3031
ReactDOM = require('react-dom');
@@ -1729,4 +1730,169 @@ describe('ReactDOMServerPartialHydration', () => {
17291730
// patched up the tree, which might mean we haven't patched the className.
17301731
expect(newSpan.className).toBe('hi');
17311732
});
1733+
1734+
it('does not invoke an event on a hydrated node until it commits', async () => {
1735+
let suspend = false;
1736+
let resolve;
1737+
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
1738+
1739+
function Sibling({text}) {
1740+
if (suspend) {
1741+
throw promise;
1742+
} else {
1743+
return 'Hello';
1744+
}
1745+
}
1746+
1747+
let clicks = 0;
1748+
1749+
function Button() {
1750+
let [clicked, setClicked] = React.useState(false);
1751+
if (clicked) {
1752+
return null;
1753+
}
1754+
return (
1755+
<a
1756+
onClick={() => {
1757+
setClicked(true);
1758+
clicks++;
1759+
}}>
1760+
Click me
1761+
</a>
1762+
);
1763+
}
1764+
1765+
function App() {
1766+
return (
1767+
<div>
1768+
<Suspense fallback="Loading...">
1769+
<Button />
1770+
<Sibling />
1771+
</Suspense>
1772+
</div>
1773+
);
1774+
}
1775+
1776+
suspend = false;
1777+
let finalHTML = ReactDOMServer.renderToString(<App />);
1778+
let container = document.createElement('div');
1779+
container.innerHTML = finalHTML;
1780+
1781+
// We need this to be in the document since we'll dispatch events on it.
1782+
document.body.appendChild(container);
1783+
1784+
let a = container.getElementsByTagName('a')[0];
1785+
1786+
// On the client we don't have all data yet but we want to start
1787+
// hydrating anyway.
1788+
suspend = true;
1789+
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
1790+
root.render(<App />);
1791+
Scheduler.unstable_flushAll();
1792+
jest.runAllTimers();
1793+
1794+
expect(container.textContent).toBe('Click meHello');
1795+
1796+
// We're now partially hydrated.
1797+
a.click();
1798+
expect(clicks).toBe(0);
1799+
1800+
// Resolving the promise so that rendering can complete.
1801+
suspend = false;
1802+
resolve();
1803+
await promise;
1804+
1805+
Scheduler.unstable_flushAll();
1806+
jest.runAllTimers();
1807+
1808+
// TODO: With selective hydration the event should've been replayed
1809+
// but for now we'll have to issue it again.
1810+
act(() => {
1811+
a.click();
1812+
});
1813+
1814+
expect(clicks).toBe(1);
1815+
1816+
expect(container.textContent).toBe('Hello');
1817+
1818+
document.body.removeChild(container);
1819+
});
1820+
1821+
it('does not invoke an event on a hydrated EventResponder until it commits', async () => {
1822+
let suspend = false;
1823+
let resolve;
1824+
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
1825+
1826+
function Sibling({text}) {
1827+
if (suspend) {
1828+
throw promise;
1829+
} else {
1830+
return 'Hello';
1831+
}
1832+
}
1833+
1834+
const onEvent = jest.fn();
1835+
const TestResponder = React.unstable_createResponder('TestEventResponder', {
1836+
targetEventTypes: ['click'],
1837+
onEvent,
1838+
});
1839+
1840+
function Button() {
1841+
let listener = React.unstable_useResponder(TestResponder, {});
1842+
return <a listeners={listener}>Click me</a>;
1843+
}
1844+
1845+
function App() {
1846+
return (
1847+
<div>
1848+
<Suspense fallback="Loading...">
1849+
<Button />
1850+
<Sibling />
1851+
</Suspense>
1852+
</div>
1853+
);
1854+
}
1855+
1856+
suspend = false;
1857+
let finalHTML = ReactDOMServer.renderToString(<App />);
1858+
let container = document.createElement('div');
1859+
container.innerHTML = finalHTML;
1860+
1861+
// We need this to be in the document since we'll dispatch events on it.
1862+
document.body.appendChild(container);
1863+
1864+
let a = container.getElementsByTagName('a')[0];
1865+
1866+
// On the client we don't have all data yet but we want to start
1867+
// hydrating anyway.
1868+
suspend = true;
1869+
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
1870+
root.render(<App />);
1871+
Scheduler.unstable_flushAll();
1872+
jest.runAllTimers();
1873+
1874+
// We're now partially hydrated.
1875+
a.click();
1876+
// We should not have invoked the event yet because we're not
1877+
// yet hydrated.
1878+
expect(onEvent).toHaveBeenCalledTimes(0);
1879+
1880+
// Resolving the promise so that rendering can complete.
1881+
suspend = false;
1882+
resolve();
1883+
await promise;
1884+
1885+
Scheduler.unstable_flushAll();
1886+
jest.runAllTimers();
1887+
1888+
// TODO: With selective hydration the event should've been replayed
1889+
// but for now we'll have to issue it again.
1890+
act(() => {
1891+
a.click();
1892+
});
1893+
1894+
expect(onEvent).toHaveBeenCalledTimes(1);
1895+
1896+
document.body.removeChild(container);
1897+
});
17321898
});

packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ let React;
1313
let ReactDOM;
1414
let ReactDOMServer;
1515
let Scheduler;
16+
let act;
1617

1718
// These tests rely both on ReactDOMServer and ReactDOM.
1819
// If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead.
@@ -23,6 +24,7 @@ describe('ReactDOMServerHydration', () => {
2324
ReactDOM = require('react-dom');
2425
ReactDOMServer = require('react-dom/server');
2526
Scheduler = require('scheduler');
27+
act = require('react-dom/test-utils').act;
2628
});
2729

2830
it('should have the correct mounting behavior (old hydrate API)', () => {
@@ -499,4 +501,89 @@ describe('ReactDOMServerHydration', () => {
499501
Scheduler.unstable_flushAll();
500502
expect(element.textContent).toBe('Hello world');
501503
});
504+
505+
it('does not invoke an event on a concurrent hydrating node until it commits', () => {
506+
function Sibling({text}) {
507+
Scheduler.unstable_yieldValue('Sibling');
508+
return <span>Sibling</span>;
509+
}
510+
511+
function Sibling2({text}) {
512+
Scheduler.unstable_yieldValue('Sibling2');
513+
return null;
514+
}
515+
516+
let clicks = 0;
517+
518+
function Button() {
519+
Scheduler.unstable_yieldValue('Button');
520+
let [clicked, setClicked] = React.useState(false);
521+
if (clicked) {
522+
return null;
523+
}
524+
return (
525+
<a
526+
onClick={() => {
527+
setClicked(true);
528+
clicks++;
529+
}}>
530+
Click me
531+
</a>
532+
);
533+
}
534+
535+
function App() {
536+
return (
537+
<div>
538+
<Button />
539+
<Sibling />
540+
<Sibling2 />
541+
</div>
542+
);
543+
}
544+
545+
let finalHTML = ReactDOMServer.renderToString(<App />);
546+
let container = document.createElement('div');
547+
container.innerHTML = finalHTML;
548+
expect(Scheduler).toHaveYielded(['Button', 'Sibling', 'Sibling2']);
549+
550+
// We need this to be in the document since we'll dispatch events on it.
551+
document.body.appendChild(container);
552+
553+
let a = container.getElementsByTagName('a')[0];
554+
555+
// Hydrate asynchronously.
556+
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
557+
root.render(<App />);
558+
// Flush part way through the render.
559+
if (__DEV__) {
560+
// In DEV effects gets double invoked.
561+
expect(Scheduler).toFlushAndYieldThrough(['Button', 'Button', 'Sibling']);
562+
} else {
563+
expect(Scheduler).toFlushAndYieldThrough(['Button', 'Sibling']);
564+
}
565+
566+
expect(container.textContent).toBe('Click meSibling');
567+
568+
// We're now partially hydrated.
569+
a.click();
570+
// Clicking should not invoke the event yet because we haven't committed
571+
// the hydration yet.
572+
expect(clicks).toBe(0);
573+
574+
// Finish the rest of the hydration.
575+
expect(Scheduler).toFlushAndYield(['Sibling2']);
576+
577+
// TODO: With selective hydration the event should've been replayed
578+
// but for now we'll have to issue it again.
579+
act(() => {
580+
a.click();
581+
});
582+
583+
expect(clicks).toBe(1);
584+
585+
expect(container.textContent).toBe('Sibling');
586+
587+
document.body.removeChild(container);
588+
});
502589
});

packages/react-dom/src/client/ReactDOMComponentTree.js

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,29 @@ export function precacheFiberNode(hostInst, node) {
2323
* ReactDOMTextComponent instance ancestor.
2424
*/
2525
export function getClosestInstanceFromNode(node) {
26-
if (node[internalInstanceKey]) {
27-
return node[internalInstanceKey];
26+
let inst = node[internalInstanceKey];
27+
if (inst) {
28+
return inst;
2829
}
2930

30-
while (!node[internalInstanceKey]) {
31-
if (node.parentNode) {
32-
node = node.parentNode;
31+
do {
32+
node = node.parentNode;
33+
if (node) {
34+
inst = node[internalInstanceKey];
3335
} else {
3436
// Top of the tree. This node must not be part of a React tree (or is
3537
// unmounted, potentially).
3638
return null;
3739
}
38-
}
40+
} while (!inst);
3941

40-
let inst = node[internalInstanceKey];
41-
if (inst.tag === HostComponent || inst.tag === HostText) {
42-
// In Fiber, this will always be the deepest root.
43-
return inst;
42+
let tag = inst.tag;
43+
switch (tag) {
44+
case HostComponent:
45+
case HostText:
46+
// In Fiber, this will always be the deepest root.
47+
return inst;
4448
}
45-
4649
return null;
4750
}
4851

packages/react-dom/src/client/ReactDOMHostConfig.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -824,10 +824,9 @@ export function mountResponderInstance(
824824
responderProps: Object,
825825
responderState: Object,
826826
instance: Instance,
827-
rootContainerInstance: Container,
828827
): ReactDOMEventResponderInstance {
829828
// Listen to events
830-
const doc = rootContainerInstance.ownerDocument;
829+
const doc = instance.ownerDocument;
831830
const documentBody = doc.body || doc;
832831
const {
833832
rootEventTypes,

0 commit comments

Comments
 (0)