[DevTools] Use the hard coded url instead of the local storage url for presets (and make VSCode default) (#33995)
Stacked on #33983. Previously, the source of truth is the url stored in local storage but that means if we change the presets then they don't take effect (e.g. #33994). This PR uses the hardcoded value instead when a preset is selected. This also has the benefit that if you switch between custom and vs code in the selector, then the custom url is preserved instead of getting reset when you checkout other options. Currently the default is custom with empty string, which means that there's no code editor configured at all by default. It doesn't make a lot of sense that we have it not working by default when so many people use VS Code. So this also makes VS Code the default if there's no EDITOR_URL env specified.
Sebastian Markbåge committed
Jul 25, 2025 at 10:27 UTC
b2c30493ce891d6e066667aa4a49c51837deaebc
6 files changed
+71
-50
packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js
+4
-11
@@ -18,16 +18,14 @@ import Toggle from '../Toggle';
18
import {ElementTypeSuspense} from 'react-devtools-shared/src/frontend/types';
19
import InspectedElementView from './InspectedElementView';
20
import {InspectedElementContext} from './InspectedElementContext';
21
-import {getOpenInEditorURL, getAlwaysOpenInEditor} from '../../../utils';
22
-import {
23
- LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
24
- LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR,
25
-} from '../../../constants';
21
+import {getAlwaysOpenInEditor} from '../../../utils';
22
+import {LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR} from '../../../constants';
23
import FetchFileWithCachingContext from './FetchFileWithCachingContext';
24
import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource';
25
import OpenInEditorButton from './OpenInEditorButton';
26
import InspectedElementViewSourceButton from './InspectedElementViewSourceButton';
27
import Skeleton from './Skeleton';
28
+import useEditorURL from '../useEditorURL';
29
30
import styles from './InspectedElement.css';
31
@@ -134,12 +132,7 @@ export default function InspectedElementWrapper(_: Props): React.Node {
132
getAlwaysOpenInEditor,
133
);
134
137
- const editorURL = useSyncExternalStore(function subscribe(callback) {
138
- window.addEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
139
- return function unsubscribe() {
140
- window.removeEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
141
- };
142
- }, getOpenInEditorURL);
135
+ const editorURL = useEditorURL();
136
137
const toggleErrored = useCallback(() => {
138
if (inspectedElement == null) {
packages/react-devtools-shared/src/devtools/views/Editor/EditorPane.js
+3
-14
@@ -8,7 +8,7 @@
8
*/
9
10
import * as React from 'react';
11
-import {useSyncExternalStore, useState, startTransition} from 'react';
11
+import {useState, startTransition} from 'react';
12
13
import portaledContent from '../portaledContent';
14
@@ -18,8 +18,7 @@ import Button from 'react-devtools-shared/src/devtools/views/Button';
18
import ButtonIcon from 'react-devtools-shared/src/devtools/views/ButtonIcon';
19
20
import OpenInEditorButton from './OpenInEditorButton';
21
-import {getOpenInEditorURL} from '../../../utils';
22
-import {LOCAL_STORAGE_OPEN_IN_EDITOR_URL} from '../../../constants';
21
+import useEditorURL from '../useEditorURL';
22
23
import EditorSettings from './EditorSettings';
24
import CodeEditorByDefault from '../Settings/CodeEditorByDefault';
@@ -39,17 +38,7 @@ function EditorPane({selectedSource}: Props) {
38
const [showSettings, setShowSettings] = useState(false);
39
const [showLinkInfo, setShowLinkInfo] = useState(false);
40
42
- const editorURL = useSyncExternalStore(
43
- function subscribe(callback) {
44
- window.addEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
45
- return function unsubscribe() {
46
- window.removeEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
47
- };
48
- },
49
- function getState() {
50
- return getOpenInEditorURL();
51
- },
52
- );
41
+ const editorURL = useEditorURL();
42
43
if (showLinkInfo) {
44
return (
packages/react-devtools-shared/src/devtools/views/Settings/CodeEditorOptions.js
+6
-10
@@ -13,12 +13,13 @@ import {
13
LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET,
14
} from '../../../constants';
15
import {useLocalStorage} from '../hooks';
16
-import {getDefaultOpenInEditorURL} from 'react-devtools-shared/src/utils';
16
+import {
17
+ getDefaultPreset,
18
+ getDefaultOpenInEditorURL,
19
+} from 'react-devtools-shared/src/utils';
20
21
import styles from './SettingsShared.css';
22
20
-const vscodeFilepath = 'vscode://file/{path}:{line}:{column}';
21
-
23
export default function CodeEditorOptions({
24
environmentNames,
25
}: {
@@ -26,7 +27,7 @@ export default function CodeEditorOptions({
27
}): React.Node {
28
const [openInEditorURLPreset, setOpenInEditorURLPreset] = useLocalStorage<
29
'vscode' | 'custom',
29
- >(LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET, 'custom');
30
+ >(LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET, getDefaultPreset());
31
32
const [openInEditorURL, setOpenInEditorURL] = useLocalStorage<string>(
33
LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
@@ -40,11 +41,6 @@ export default function CodeEditorOptions({
41
onChange={({currentTarget}) => {
42
const selectedValue = currentTarget.value;
43
setOpenInEditorURLPreset(selectedValue);
43
- if (selectedValue === 'vscode') {
44
- setOpenInEditorURL(vscodeFilepath);
45
- } else if (selectedValue === 'custom') {
46
- setOpenInEditorURL('');
47
- }
44
}}>
45
<option value="vscode">VS Code</option>
46
<option value="custom">Custom</option>
@@ -53,7 +49,7 @@ export default function CodeEditorOptions({
49
<input
50
className={styles.Input}
51
type="text"
56
- placeholder={process.env.EDITOR_URL ? process.env.EDITOR_URL : ''}
52
+ placeholder={getDefaultOpenInEditorURL()}
53
value={openInEditorURL}
54
onChange={event => {
55
setOpenInEditorURL(event.target.value);
packages/react-devtools-shared/src/devtools/views/useEditorURL.js
new
+39
@@ -0,0 +1,39 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import {useCallback, useSyncExternalStore} from 'react';
11
+
12
+import {getOpenInEditorURL} from '../../utils';
13
+import {
14
+ LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
15
+ LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET,
16
+} from '../../constants';
17
+
18
+const useEditorURL = (): string => {
19
+ const editorURL = useSyncExternalStore(
20
+ useCallback(function subscribe(callback) {
21
+ window.addEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
22
+ window.addEventListener(
23
+ LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET,
24
+ callback,
25
+ );
26
+ return function unsubscribe() {
27
+ window.removeEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
28
+ window.removeEventListener(
29
+ LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET,
30
+ callback,
31
+ );
32
+ };
33
+ }, []),
34
+ getOpenInEditorURL,
35
+ );
36
+ return editorURL;
37
+};
38
+
39
+export default useEditorURL;
packages/react-devtools-shared/src/devtools/views/useOpenResource.js
+4
-14
@@ -13,11 +13,9 @@ import {useCallback, useContext, useSyncExternalStore} from 'react';
13
14
import ViewElementSourceContext from './Components/ViewElementSourceContext';
15
16
-import {getOpenInEditorURL, getAlwaysOpenInEditor} from '../../utils';
17
-import {
18
- LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
19
- LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR,
20
-} from '../../constants';
16
+import {getAlwaysOpenInEditor} from '../../utils';
17
+import useEditorURL from './useEditorURL';
18
+import {LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR} from '../../constants';
19
20
import {checkConditions} from './Editor/utils';
21
@@ -32,15 +30,7 @@ const useOpenResource = (
30
ViewElementSourceContext,
31
);
32
35
- const editorURL = useSyncExternalStore(
36
- useCallback(function subscribe(callback) {
37
- window.addEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
38
- return function unsubscribe() {
39
- window.removeEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback);
40
- };
41
- }, []),
42
- getOpenInEditorURL,
43
- );
33
+ const editorURL = useEditorURL();
34
35
const alwaysOpenInEditor = useSyncExternalStore(
36
useCallback(function subscribe(callback) {
packages/react-devtools-shared/src/utils.js
+15
-1
@@ -35,6 +35,7 @@ import {
35
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
36
LOCAL_STORAGE_COMPONENT_FILTER_PREFERENCES_KEY,
37
LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
38
+ LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET,
39
LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR,
40
SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
41
SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY,
@@ -385,14 +386,27 @@ export function filterOutLocationComponentFilters(
386
return componentFilters.filter(f => f.type !== ComponentFilterLocation);
387
}
388
389
+const vscodeFilepath = 'vscode://file/{path}:{line}:{column}';
390
+
391
+export function getDefaultPreset(): 'custom' | 'vscode' {
392
+ return typeof process.env.EDITOR_URL === 'string' ? 'custom' : 'vscode';
393
+}
394
+
395
export function getDefaultOpenInEditorURL(): string {
396
return typeof process.env.EDITOR_URL === 'string'
397
? process.env.EDITOR_URL
391
- : '';
398
+ : vscodeFilepath;
399
}
400
401
export function getOpenInEditorURL(): string {
402
try {
403
+ const rawPreset = localStorageGetItem(
404
+ LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET,
405
+ );
406
+ switch (rawPreset) {
407
+ case '"vscode"':
408
+ return vscodeFilepath;
409
+ }
410
const raw = localStorageGetItem(LOCAL_STORAGE_OPEN_IN_EDITOR_URL);
411
if (raw != null) {
412
return JSON.parse(raw);