Skip to content

Commit 833f206

Browse files
author
Brian Vaughn
committed
Merge branch 'master' into devtools-v4-merge
2 parents 85fbe3b + efa5dbe commit 833f206

Some content is hidden

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

52 files changed

+2490
-3096
lines changed

packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js

Lines changed: 114 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,17 @@ ESLintTester.setDefaultConfig({
1919
},
2020
});
2121

22-
const eslintTester = new ESLintTester();
23-
eslintTester.run('react-hooks', ReactHooksESLintRule, {
22+
// ***************************************************
23+
// For easier local testing, you can add to any case:
24+
// {
25+
// skip: true,
26+
// --or--
27+
// only: true,
28+
// ...
29+
// }
30+
// ***************************************************
31+
32+
const tests = {
2433
valid: [
2534
`
2635
// Valid because components can use hooks.
@@ -223,21 +232,20 @@ eslintTester.run('react-hooks', ReactHooksESLintRule, {
223232
(class {i() { useState(); }});
224233
`,
225234
`
226-
// Currently valid although we *could* consider these invalid.
227-
// It doesn't make a lot of difference because it would crash early.
235+
// Valid because they're not matching use[A-Z].
236+
fooState();
228237
use();
229238
_use();
230-
useState();
231239
_useState();
232-
use42();
233-
useHook();
234240
use_hook();
235-
React.useState();
236241
`,
237242
`
238-
// Regression test for the popular "history" library
239-
const {createHistory, useBasename} = require('history-2.1.2');
240-
const browserHistory = useBasename(createHistory)({
243+
// This is grey area.
244+
// Currently it's valid (although React.useCallback would fail here).
245+
// We could also get stricter and disallow it, just like we did
246+
// with non-namespace use*() top-level calls.
247+
const History = require('history-2.1.2');
248+
const browserHistory = History.useBasename(History.createHistory)({
241249
basename: '/',
242250
});
243251
`,
@@ -669,8 +677,63 @@ eslintTester.run('react-hooks', ReactHooksESLintRule, {
669677
conditionalError('useState'),
670678
],
671679
},
680+
{
681+
code: `
682+
// Invalid because it's dangerous and might not warn otherwise.
683+
// This *must* be invalid.
684+
function useHook({ bar }) {
685+
let foo1 = bar && useState();
686+
let foo2 = bar || useState();
687+
let foo3 = bar ?? useState();
688+
}
689+
`,
690+
errors: [
691+
conditionalError('useState'),
692+
conditionalError('useState'),
693+
// TODO: ideally this *should* warn, but ESLint
694+
// doesn't plan full support for ?? until it advances.
695+
// conditionalError('useState'),
696+
],
697+
},
698+
{
699+
code: `
700+
// Invalid because it's dangerous.
701+
// Normally, this would crash, but not if you use inline requires.
702+
// This *must* be invalid.
703+
// It's expected to have some false positives, but arguably
704+
// they are confusing anyway due to the use*() convention
705+
// already being associated with Hooks.
706+
useState();
707+
if (foo) {
708+
const foo = React.useCallback(() => {});
709+
}
710+
useCustomHook();
711+
`,
712+
errors: [
713+
topLevelError('useState'),
714+
topLevelError('React.useCallback'),
715+
topLevelError('useCustomHook'),
716+
],
717+
},
718+
{
719+
code: `
720+
// Technically this is a false positive.
721+
// We *could* make it valid (and it used to be).
722+
//
723+
// However, top-level Hook-like calls can be very dangerous
724+
// in environments with inline requires because they can mask
725+
// the runtime error by accident.
726+
// So we prefer to disallow it despite the false positive.
727+
728+
const {createHistory, useBasename} = require('history-2.1.2');
729+
const browserHistory = useBasename(createHistory)({
730+
basename: '/',
731+
});
732+
`,
733+
errors: [topLevelError('useBasename')],
734+
},
672735
],
673-
});
736+
};
674737

675738
function conditionalError(hook, hasPreviousFinalizer = false) {
676739
return {
@@ -708,3 +771,42 @@ function genericError(hook) {
708771
'Hook function.',
709772
};
710773
}
774+
775+
function topLevelError(hook) {
776+
return {
777+
message:
778+
`React Hook "${hook}" cannot be called at the top level. React Hooks ` +
779+
'must be called in a React function component or a custom React ' +
780+
'Hook function.',
781+
};
782+
}
783+
784+
// For easier local testing
785+
if (!process.env.CI) {
786+
let only = [];
787+
let skipped = [];
788+
[...tests.valid, ...tests.invalid].forEach(t => {
789+
if (t.skip) {
790+
delete t.skip;
791+
skipped.push(t);
792+
}
793+
if (t.only) {
794+
delete t.only;
795+
only.push(t);
796+
}
797+
});
798+
const predicate = t => {
799+
if (only.length > 0) {
800+
return only.indexOf(t) !== -1;
801+
}
802+
if (skipped.length > 0) {
803+
return skipped.indexOf(t) === -1;
804+
}
805+
return true;
806+
};
807+
tests.valid = tests.valid.filter(predicate);
808+
tests.invalid = tests.invalid.filter(predicate);
809+
}
810+
811+
const eslintTester = new ESLintTester();
812+
eslintTester.run('react-hooks', ReactHooksESLintRule, tests);

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,9 +432,13 @@ export default {
432432
'React Hook function.';
433433
context.report({node: hook, message});
434434
} else if (codePathNode.type === 'Program') {
435-
// For now, ignore if it's in top level scope.
436435
// We could warn here but there are false positives related
437436
// configuring libraries like `history`.
437+
const message =
438+
`React Hook "${context.getSource(hook)}" cannot be called ` +
439+
'at the top level. React Hooks must be called in a ' +
440+
'React function component or a custom React Hook function.';
441+
context.report({node: hook, message});
438442
} else {
439443
// Assume in all other cases the user called a hook in some
440444
// random function callback. This should usually be true for

packages/legacy-events/ReactGenericBatching.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
} from './ReactControlledComponent';
1212
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
1313

14+
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
15+
1416
// Used as a way to call batchedUpdates when we don't have a reference to
1517
// the renderer. Such as when we're dispatching events or if third party
1618
// libraries need to call batchedUpdates. Eventually, this API will go away when
@@ -28,6 +30,7 @@ let flushDiscreteUpdatesImpl = function() {};
2830
let batchedEventUpdatesImpl = batchedUpdatesImpl;
2931

3032
let isInsideEventHandler = false;
33+
let isBatchingEventUpdates = false;
3134

3235
function finishEventHandler() {
3336
// Here we wait until all updates have propagated, which is important
@@ -60,20 +63,31 @@ export function batchedUpdates(fn, bookkeeping) {
6063
}
6164

6265
export function batchedEventUpdates(fn, a, b) {
63-
if (isInsideEventHandler) {
66+
if (isBatchingEventUpdates) {
6467
// If we are currently inside another batch, we need to wait until it
6568
// fully completes before restoring state.
6669
return fn(a, b);
6770
}
68-
isInsideEventHandler = true;
71+
isBatchingEventUpdates = true;
6972
try {
7073
return batchedEventUpdatesImpl(fn, a, b);
7174
} finally {
72-
isInsideEventHandler = false;
75+
isBatchingEventUpdates = false;
7376
finishEventHandler();
7477
}
7578
}
7679

80+
export function executeUserEventHandler(fn: any => void, value: any) {
81+
const previouslyInEventHandler = isInsideEventHandler;
82+
try {
83+
isInsideEventHandler = true;
84+
const type = typeof value === 'object' && value !== null ? value.type : '';
85+
invokeGuardedCallbackAndCatchFirstError(type, fn, undefined, value);
86+
} finally {
87+
isInsideEventHandler = previouslyInEventHandler;
88+
}
89+
}
90+
7791
export function discreteUpdates(fn, a, b, c) {
7892
const prevIsInsideEventHandler = isInsideEventHandler;
7993
isInsideEventHandler = true;

packages/react-art/src/ReactART.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class Surface extends React.Component {
6666

6767
this._surface = Mode.Surface(+width, +height, this._tagRef);
6868

69-
this._mountNode = createContainer(this._surface, LegacyRoot, false);
69+
this._mountNode = createContainer(this._surface, LegacyRoot, false, null);
7070
updateContainer(this.props.children, this._mountNode, this);
7171
}
7272

packages/react-devtools/CHANGELOG.md

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,47 @@
11
# React DevTools changelog
22

3-
<!-- ## [Unreleased]
43
<details>
54
<summary>
65
Changes that have landed in master but are not yet released.
76
Click to see more.
87
</summary>
98

10-
<!-- Upcoming changes go here
11-
</details> -->
9+
<!-- Upcoming changes go here -->
10+
</details>
1211

13-
## 4.0.0 (release date TBD)
12+
## 4.0.5 (August 19, 2019)
13+
#### Bug fixes
14+
* Props, state, and context values are alpha sorted.
15+
* Standalone DevTools properly serves backend script over localhost:8097
16+
17+
## 4.0.4 (August 18, 2019)
18+
#### Bug fixes
19+
* Bugfix for potential error if a min-duration commit filter is applied after selecting a fiber in the Profiler UI.
20+
21+
## 4.0.3 (August 17, 2019)
22+
#### Bug fixes
23+
* ES6 `Map` and `Set`, typed arrays, and other unserializable types (e.g. Immutable JS) can now be inspected.
24+
* Empty objects and arrays now display an "(empty)" label to the right to reduce confusion.
25+
* Components that use only the `useContext` hook now properly display hooks values in side panel.
26+
* Style editor now supports single quotes around string values (e.g. both `"red"` and `'red'`).
27+
* Fixed edge case bug that prevented profiling when both React v16 and v15 were present on a page.
28+
29+
## 4.0.2 (August 15, 2019)
30+
#### Permissions cleanup
31+
* Removed unnecessary `webNavigation ` permission from Chrome and Firefox extensions.
32+
33+
## 4.0.1 (August 15, 2019)
34+
#### Permissions cleanup
35+
* Removed unnecessary `<all_urls>`, `background`, and `tabs` permissions from Chrome and Firefox extensions.
36+
37+
## 4.0.0 (August 15, 2019)
1438

1539
### General changes
1640

1741
#### Improved performance
1842
The legacy DevTools extension used to add significant performance overhead, making it unusable for some larger React applications. That overhead has been effectively eliminated in version 4.
1943

20-
[Learn more](https://github.com/bvaughn/react-devtools-experimental/blob/master/OVERVIEW.md) about the performance optimizations that made this possible.
44+
[Learn more](https://github.com/facebook/react/blob/master/packages/react-devtools/OVERVIEW.md) about the performance optimizations that made this possible.
2145

2246
#### Component stacks
2347

@@ -41,17 +65,17 @@ Host nodes (e.g. HTML `<div>`, React Native `View`) are now hidden by default, b
4165

4266
Filter preferences are remembered between sessions.
4367

44-
#### No more in-line props
68+
#### No more inline props
4569

46-
Components in the tree no longer show in-line props. This was done to [make DevTools faster](https://github.com/bvaughn/react-devtools-experimental/blob/master/OVERVIEW.md) and to make it easier to browse larger component trees.
70+
Components in the tree no longer show inline props. This was done to [make DevTools faster](https://github.com/facebook/react/blob/master/packages/react-devtools/OVERVIEW.md) and to make it easier to browse larger component trees.
4771

4872
You can view a component's props, state, and hooks by selecting it:
4973

5074
![Inspecting props](https://user-images.githubusercontent.com/29597/62303001-37da6400-b430-11e9-87fd-10a94df88efa.png)
5175

5276
#### "Rendered by" list
5377

54-
In React, an element's "owner" refers the thing that rendered it. Sometimes an element's parent is also its owner, but usually they're different. This distinction is important because props come from owners.
78+
In React, an element's "owner" refers to the thing that rendered it. Sometimes an element's parent is also its owner, but usually they're different. This distinction is important because props come from owners.
5579

5680
![Example code](https://user-images.githubusercontent.com/29597/62229551-bbcf1600-b374-11e9-8411-8ff411f4f847.png)
5781

@@ -101,6 +125,12 @@ Components decorated with multiple HOCs show the topmost badge and a count. Sele
101125

102126
![Screenshot showing a component with multiple HOC badges](https://user-images.githubusercontent.com/29597/62303729-7fadbb00-b431-11e9-8685-45f5ab52b30b.png)
103127

128+
#### Restoring selection between reloads
129+
130+
DevTools now attempts to restore the previously selected element when you reload the page.
131+
132+
![Video demonstrating selection persistence](https://user-images.githubusercontent.com/810438/63130054-2c02ac00-bfb1-11e9-92fa-382e9e433638.gif)
133+
104134
#### Suspense toggle
105135

106136
React's experimental [Suspense API](https://reactjs.org/docs/react-api.html#suspense) lets components "wait" for something before rendering. `<Suspense>` components can be used to specify loading states when components deeper in the tree are waiting to render.

packages/react-devtools/OVERVIEW.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ Even when dealing with a single component, serializing deeply nested properties
229229

230230
Hooks present a unique challenge for the DevTools because of the concept of _custom_ hooks. (A custom hook is essentially any function that calls at least one of the built-in hooks. By convention custom hooks also have names that begin with "use".)
231231

232-
So how does DevTools identify custom functions called from within third party components? It does this by temporarily overriding React's built-in hooks and shallow rendering the component in question. Whenever one of the (overridden) built-in hooks are called, it parses the call stack to spot potential custom hooks (functions between the component itself and the built-in hook). This approach enables it to build a tree structure describing all of the calls to both the built-in _and_ custom hooks, along with the values passed to those hooks. (If you're interested in learning more about this, [here is the source code](https://github.com/bvaughn/react-devtools-experimental/blob/master/src/backend/ReactDebugHooks.js).)
232+
So how does DevTools identify custom functions called from within third party components? It does this by temporarily overriding React's built-in hooks and shallow rendering the component in question. Whenever one of the (overridden) built-in hooks are called, it parses the call stack to spot potential custom hooks (functions between the component itself and the built-in hook). This approach enables it to build a tree structure describing all of the calls to both the built-in _and_ custom hooks, along with the values passed to those hooks. (If you're interested in learning more about this, [here is the source code](https://github.com/facebook/react/blob/master/packages/react-debug-tools/src/ReactDebugHooks.js).)
233233

234234
> **Note**: DevTools obtains hooks info by re-rendering a component.
235235
> Breakpoints will be invoked during this additional (shallow) render,

0 commit comments

Comments
 (0)