diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json
index 8678701e7..fabf52bc2 100644
--- a/.codesandbox/ci.json
+++ b/.codesandbox/ci.json
@@ -1,5 +1,5 @@
{
"packages": ["sandpack-client", "sandpack-react"],
- "sandboxes": ["sowx8r"],
+ "sandboxes": ["sowx8r", "909l3f"],
"node": "16"
}
diff --git a/sandpack-client/package.json b/sandpack-client/package.json
index 6cc10e37d..b22535c14 100644
--- a/sandpack-client/package.json
+++ b/sandpack-client/package.json
@@ -47,11 +47,10 @@
],
"dependencies": {
"@codesandbox/nodebox": "0.1.0",
- "lodash.isequal": "^4.5.0",
+ "dequal": "^2.0.2",
"outvariant": "1.3.0"
},
"devDependencies": {
- "@types/lodash.isequal": "^4.5.2",
"@types/node": "^9.3.0",
"console-feed": "3.3.0",
"del": "^6.0.0",
diff --git a/sandpack-client/src/clients/base.ts b/sandpack-client/src/clients/base.ts
index 3ab17316a..366bd8c72 100644
--- a/sandpack-client/src/clients/base.ts
+++ b/sandpack-client/src/clients/base.ts
@@ -1,4 +1,4 @@
-import isEqual from "lodash.isequal";
+import { dequal as deepEqual } from "dequal";
import type {
ClientOptions,
@@ -35,7 +35,7 @@ export class SandpackClient {
* Clients handles
*/
public updateOptions(options: ClientOptions): void {
- if (!isEqual(this.options, options)) {
+ if (!deepEqual(this.options, options)) {
this.options = options;
this.updateSandbox();
}
diff --git a/sandpack-client/src/clients/runtime/index.ts b/sandpack-client/src/clients/runtime/index.ts
index bd4154ec5..44365ad7e 100644
--- a/sandpack-client/src/clients/runtime/index.ts
+++ b/sandpack-client/src/clients/runtime/index.ts
@@ -1,4 +1,4 @@
-import isEqual from "lodash.isequal";
+import { dequal as deepEqual } from "dequal";
import type { SandpackMessage } from "../..";
import { nullthrows } from "../..";
@@ -163,7 +163,7 @@ export class SandpackRuntime extends SandpackClient {
}
updateOptions(options: ClientOptions): void {
- if (!isEqual(this.options, options)) {
+ if (!deepEqual(this.options, options)) {
this.options = options;
this.updateSandbox();
}
diff --git a/sandpack-react/package.json b/sandpack-react/package.json
index 7e0954d00..3650886ff 100644
--- a/sandpack-react/package.json
+++ b/sandpack-react/package.json
@@ -35,6 +35,7 @@
"README.md"
],
"dependencies": {
+ "use-deep-compare-effect": "1.8.1",
"@code-hike/classer": "^0.0.0-aa6efee",
"@codemirror/autocomplete": "^6.4.0",
"@codemirror/commands": "^6.1.3",
@@ -51,7 +52,7 @@
"ansi-to-react": "6.1.6",
"clean-set": "^1.1.2",
"codesandbox-import-util-types": "^2.2.3",
- "lodash.isequal": "^4.5.0",
+ "dequal": "^2.0.2",
"lz-string": "^1.4.4",
"react-devtools-inline": "4.4.0",
"react-is": "^17.0.2"
@@ -66,7 +67,6 @@
"@testing-library/react-hooks": "8.0.1",
"@types/fs-extra": "^5.0.4",
"@types/glob": "^5.0.35",
- "@types/lodash.isequal": "^4.5.2",
"@types/lz-string": "^1.3.34",
"@types/node": "^9.4.6",
"@types/react": "^18.0.15",
diff --git a/sandpack-react/src/Playground.stories.tsx b/sandpack-react/src/Playground.stories.tsx
index d42734b9f..19a211578 100644
--- a/sandpack-react/src/Playground.stories.tsx
+++ b/sandpack-react/src/Playground.stories.tsx
@@ -1,352 +1,31 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as themes from "@codesandbox/sandpack-themes";
-import React, { useState } from "react";
+import React from "react";
-import type { CodeEditorProps } from "./components/CodeEditor";
import { Sandpack } from "./presets";
-import { SANDBOX_TEMPLATES } from "./templates";
-
-import {
- SandpackProvider,
- SandpackCodeEditor,
- SandpackPreview,
- SandpackLayout,
- SandpackFileExplorer,
- SandpackConsole,
- SandpackTests,
-} from "./";
export default {
title: "Intro/Playground",
};
-export const Resizable = (): JSX.Element => {
+export const Basic: React.FC = () => {
+ const [text, setText] = React.useState("world");
return (
<>
-
-
-
-
-
-
![Modern building architecture](https://images.unsplash.com/photo-1637734433731-621aca1c8cb6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=404&q=80)
-
-
-
- Company retreats
-
-
- Incredible accommodation for your team
-
-
- Looking to take your team away on a retreat to enjoy awesome food
- and take in some sunshine? We have a list of places to do just that.
-
-
-
-
-
-
-
- );
-}`,
- }}
- options={{
- externalResources: ["https://cdn.tailwindcss.com"],
- showLineNumbers: true,
- showConsole: false,
- showConsoleButton: true,
- resizablePanels: true,
- editorHeight: 700,
- }}
- template="react"
- />
-
- >
- );
-};
-
-export const Main = (): JSX.Element => {
- const [config, setConfig] = useState({
- Components: {
- Preview: true,
- Editor: true,
- FileExplorer: true,
- Console: true,
- Tests: true,
- },
- Options: {
- showTabs: true,
- showLineNumbers: true,
- showInlineErrors: true,
- closableTabs: true,
- wrapContent: false,
- readOnly: false,
- showReadOnly: true,
- showNavigator: true,
- showRefreshButton: true,
- consoleShowHeader: true,
- showConsoleButton: true,
- showConsole: true,
- },
- Template: "exhaustedFilesTests" as const,
- Theme: "light",
- });
-
- const update = (key: any, value: any): void => {
- setConfig((prev) => ({ ...prev, [key]: value }));
- };
-
- const toggle = (key: any, value: any): void => {
- setConfig((prev) => {
- return {
- ...prev,
- [key]: { ...prev[key], [value]: !prev[key][value] },
- };
- });
- };
-
- const codeEditorOptions: CodeEditorProps = {
- showTabs: config.Options.showTabs,
- showLineNumbers: config.Options.showLineNumbers,
- showInlineErrors: config.Options.showInlineErrors,
- wrapContent: config.Options.wrapContent,
- closableTabs: config.Options.closableTabs,
- readOnly: config.Options.readOnly,
- showReadOnly: config.Options.showReadOnly,
- };
-
- return (
-
-
- {Object.entries(config).map(([key, value]) => {
- if (typeof value === "string") {
- if (key === "Template") {
- return (
-
-
Template
-
-
- );
- }
-
- if (key === "Theme") {
- return (
-
-
Themes
-
-
- );
- }
-
- return value;
- }
-
- return (
- <>
-
{key}
- {Object.entries(value).map(([prop, propValue]) => {
- return (
-
-
-
- );
- })}
- >
- );
- })}
-
-
-
-
-
-
- {config.Components.FileExplorer && }
- {config.Components.Editor && (
-
- )}
- {config.Components.Preview && (
-
- )}
-
- {config.Components.Console && (
-
- )}
- {config.Components.Tests && }
-
-
-
-
-
-
-
-
-
- );
-};
-
-const defaultTemplate = SANDBOX_TEMPLATES["react-ts"];
-
-const exhaustedFilesTests = {
- ...defaultTemplate,
-
- files: {
- "/src/index.tsx": defaultTemplate.files["/index.tsx"],
- "/src/App.tsx": `console.log("Hello world");\n\n${defaultTemplate.files["/App.tsx"].code}`,
- "/src/App.test.tsx": `import '@testing-library/jest-dom';
-import React from 'react';
-import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders welcome message', () => {
- render();
- expect(screen.getByText('Hello world')).toBeInTheDocument();
-});`,
- "/src/styles.css": defaultTemplate.files["/styles.css"],
- "/package.json": JSON.stringify({
- dependencies: {
- react: "^18.0.0",
- "react-dom": "^18.0.0",
- "react-scripts": "^4.0.0",
- "@testing-library/react": "^13.3.0",
- "@testing-library/jest-dom": "^5.16.5",
- },
- devDependencies: {
- "@types/react": "^18.0.0",
- "@types/react-dom": "^18.0.0",
- typescript: "^4.0.0",
- },
- main: "/add.ts",
- }),
- },
-};
-
-export const BasicConsole: React.FC = () => {
- const files = {
- "/index.js": `const helloWorld = "";
-
-console.log("foo");`,
-
- "/.eslintrc.js": `module.exports = {
- rules: {
- "no-unused-vars": "error",
- "no-console": "error",
- },
- parserOptions: {
- ecmaVersion: 'latest',
- sourceType: 'module'
- },
+ setText(target.value)}
+ />
+ Hello ${text}
}
`,
-
- "/package.json": JSON.stringify({
- devDependencies: {
- eslint: "^8.0.1",
- },
- scripts: { start: "eslint index.js" },
- }),
- };
-
- return (
-
-
-
-
-
-
+ }}
+ template="react"
+ theme={themes.sandpackDark}
+ />
+ >
);
};
-
-export const Basic: React.FC = () => {
- return ;
-};
diff --git a/sandpack-react/src/components/Console/Console.stories.tsx b/sandpack-react/src/components/Console/Console.stories.tsx
index 598334e76..81fc277fc 100644
--- a/sandpack-react/src/components/Console/Console.stories.tsx
+++ b/sandpack-react/src/components/Console/Console.stories.tsx
@@ -3,7 +3,8 @@ import React from "react";
import { SandpackCodeEditor, SandpackPreview } from "..";
import { SandpackProvider, SandpackLayout, Sandpack } from "../..";
-import { SandpackConsole, SandpackConsoleRef } from "./SandpackConsole";
+import type { SandpackConsoleRef } from "./SandpackConsole";
+import { SandpackConsole } from "./SandpackConsole";
export default {
title: "components/Console",
diff --git a/sandpack-react/src/contexts/utils/useAppState.ts b/sandpack-react/src/contexts/utils/useAppState.ts
index 7d171764b..edba3dec8 100644
--- a/sandpack-react/src/contexts/utils/useAppState.ts
+++ b/sandpack-react/src/contexts/utils/useAppState.ts
@@ -1,5 +1,5 @@
import type { SandpackBundlerFiles } from "@codesandbox/sandpack-client";
-import isEqual from "lodash.isequal";
+import { dequal as deepEqual } from "dequal";
import { useState } from "react";
import type { SandpackProviderProps } from "../..";
@@ -20,7 +20,7 @@ export const useAppState: UseAppState = (props, files) => {
});
const originalStateFromProps = getSandpackStateFromProps(props);
- const editorState = isEqual(originalStateFromProps.files, files)
+ const editorState = deepEqual(originalStateFromProps.files, files)
? "pristine"
: "dirty";
diff --git a/sandpack-react/src/contexts/utils/useClient.ts b/sandpack-react/src/contexts/utils/useClient.ts
index 95ccc1a47..55f58879b 100644
--- a/sandpack-react/src/contexts/utils/useClient.ts
+++ b/sandpack-react/src/contexts/utils/useClient.ts
@@ -65,16 +65,19 @@ type UseClient = (
filesState: FilesState
) => [SandpackConfigState, UseClientOperations];
-export const useClient: UseClient = (props, filesState) => {
- const initModeFromProps = props.options?.initMode || "lazy";
+export const useClient: UseClient = ({ options, customSetup }, filesState) => {
+ options ??= {};
+ customSetup ??= {};
+
+ const initModeFromProps = options?.initMode || "lazy";
const [state, setState] = useState({
- startRoute: props.options?.startRoute,
+ startRoute: options?.startRoute,
bundlerState: undefined,
error: null,
initMode: initModeFromProps,
reactDevTools: undefined,
- status: props.options?.autorun ?? true ? "initial" : "idle",
+ status: options?.autorun ?? true ? "initial" : "idle",
});
/**
@@ -106,7 +109,10 @@ export const useClient: UseClient = (props, filesState) => {
iframe: HTMLIFrameElement,
clientId: string
): Promise => {
- const timeOut = props.options?.bundlerTimeOut ?? BUNDLER_TIMEOUT;
+ options ??= {};
+ customSetup ??= {};
+
+ const timeOut = options?.bundlerTimeOut ?? BUNDLER_TIMEOUT;
if (timeoutHook.current) {
clearTimeout(timeoutHook.current);
@@ -123,17 +129,17 @@ export const useClient: UseClient = (props, filesState) => {
template: filesState.environment,
},
{
- externalResources: props.options?.externalResources,
- bundlerURL: props.options?.bundlerURL,
- startRoute: props.options?.startRoute,
- fileResolver: props.options?.fileResolver,
- skipEval: props.options?.skipEval ?? false,
- logLevel: props.options?.logLevel,
+ externalResources: options.externalResources,
+ bundlerURL: options.bundlerURL,
+ startRoute: options.startRoute,
+ fileResolver: options.fileResolver,
+ skipEval: options.skipEval ?? false,
+ logLevel: options.logLevel,
showOpenInCodeSandbox: false,
showErrorScreen: errorScreenRegisteredRef.current,
showLoadingScreen: loadingScreenRegisteredRef.current,
reactDevTools: state.reactDevTools,
- customNpmRegistries: props.customSetup?.npmRegistries?.map(
+ customNpmRegistries: customSetup.npmRegistries?.map(
(config) =>
({
...config,
@@ -187,18 +193,7 @@ export const useClient: UseClient = (props, filesState) => {
return client;
},
- [
- filesState.environment,
- filesState.files,
- props.customSetup?.npmRegistries,
- props.options?.bundlerURL,
- props.options?.externalResources,
- props.options?.fileResolver,
- props.options?.logLevel,
- props.options?.skipEval,
- props.options?.startRoute,
- state.reactDevTools,
- ]
+ [filesState.environment, filesState.files, state.reactDevTools]
);
const unregisterAllClients = useCallback((): void => {
@@ -222,13 +217,13 @@ export const useClient: UseClient = (props, filesState) => {
}, [createClient]);
const initializeSandpackIframe = useCallback((): void => {
- const autorun = props.options?.autorun ?? true;
+ const autorun = options?.autorun ?? true;
if (!autorun) {
return;
}
- const observerOptions = props.options?.initModeObserverOptions ?? {
+ const observerOptions = options?.initModeObserverOptions ?? {
rootMargin: `1000px 0px`,
};
@@ -278,23 +273,23 @@ export const useClient: UseClient = (props, filesState) => {
);
}
}, [
- props.options?.autorun,
- props.options?.initModeObserverOptions,
+ options?.autorun,
+ options?.initModeObserverOptions,
runSandpack,
state.initMode,
unregisterAllClients,
]);
- const registerBundler = async (
- iframe: HTMLIFrameElement,
- clientId: string
- ): Promise => {
- if (state.status === "running") {
- clients.current[clientId] = await createClient(iframe, clientId);
- } else {
- preregisteredIframes.current[clientId] = iframe;
- }
- };
+ const registerBundler = useCallback(
+ async (iframe: HTMLIFrameElement, clientId: string): Promise => {
+ if (state.status === "running") {
+ clients.current[clientId] = await createClient(iframe, clientId);
+ } else {
+ preregisteredIframes.current[clientId] = iframe;
+ }
+ },
+ [createClient, state.status]
+ );
const unregisterBundler = (clientId: string): void => {
const client = clients.current[clientId];
@@ -354,56 +349,8 @@ export const useClient: UseClient = (props, filesState) => {
setState((prev) => ({ ...prev, reactDevTools: value }));
};
- const recompileMode = props.options?.recompileMode ?? "delayed";
- const recompileDelay = props.options?.recompileDelay ?? 500;
- const updateClients = useCallback((): void => {
- if (state.status !== "running" || !filesState.shouldUpdatePreview) {
- return;
- }
-
- /**
- * When the environment changes, Sandpack needs to make sure
- * to create a new client and the proper bundler
- */
- if (prevEnvironment.current !== filesState.environment) {
- prevEnvironment.current = filesState.environment;
-
- Object.entries(clients.current).forEach(([key, client]) => {
- registerBundler(client.iframe, key);
- });
- }
-
- if (recompileMode === "immediate") {
- Object.values(clients.current).forEach((client) => {
- client.updateSandbox({
- files: filesState.files,
- template: filesState.environment,
- });
- });
- }
-
- if (recompileMode === "delayed") {
- if (typeof window === "undefined") return;
-
- window.clearTimeout(debounceHook.current);
- debounceHook.current = window.setTimeout(() => {
- Object.values(clients.current).forEach((client) => {
- client.updateSandbox({
- files: filesState.files,
- template: filesState.environment,
- });
- });
- }, recompileDelay);
- }
- }, [
- filesState.shouldUpdatePreview,
- filesState.files,
- filesState.environment,
- recompileMode,
- recompileDelay,
- state.status,
- createClient,
- ]);
+ const recompileMode = options?.recompileMode ?? "delayed";
+ const recompileDelay = options?.recompileDelay ?? 500;
const dispatchMessage = (
message: SandpackMessage,
@@ -499,11 +446,57 @@ export const useClient: UseClient = (props, filesState) => {
/**
* Effects
*/
+
useEffect(
function watchFileChanges() {
- updateClients();
+ if (state.status !== "running" || !filesState.shouldUpdatePreview) {
+ return;
+ }
+
+ /**
+ * When the environment changes, Sandpack needs to make sure
+ * to create a new client and the proper bundler
+ */
+ if (prevEnvironment.current !== filesState.environment) {
+ prevEnvironment.current = filesState.environment;
+
+ Object.entries(clients.current).forEach(([key, client]) => {
+ registerBundler(client.iframe, key);
+ });
+ }
+
+ if (recompileMode === "immediate") {
+ Object.values(clients.current).forEach((client) => {
+ client.updateSandbox({
+ files: filesState.files,
+ template: filesState.environment,
+ });
+ });
+ }
+
+ if (recompileMode === "delayed") {
+ if (typeof window === "undefined") return;
+
+ window.clearTimeout(debounceHook.current);
+ debounceHook.current = window.setTimeout(() => {
+ Object.values(clients.current).forEach((client) => {
+ client.updateSandbox({
+ files: filesState.files,
+ template: filesState.environment,
+ });
+ });
+ }, recompileDelay);
+ }
},
- [filesState.files, filesState.environment, updateClients]
+ [
+ filesState.files,
+ filesState.environment,
+ filesState.shouldUpdatePreview,
+ recompileDelay,
+ recompileMode,
+ registerBundler,
+ state.status,
+ ]
);
useEffect(
diff --git a/sandpack-react/src/contexts/utils/useFiles.ts b/sandpack-react/src/contexts/utils/useFiles.ts
index f89045529..f5a6fd530 100644
--- a/sandpack-react/src/contexts/utils/useFiles.ts
+++ b/sandpack-react/src/contexts/utils/useFiles.ts
@@ -1,7 +1,7 @@
import type { SandpackBundlerFiles } from "@codesandbox/sandpack-client";
import { normalizePath } from "@codesandbox/sandpack-client";
-import isEqual from "lodash.isequal";
-import { useRef, useState } from "react";
+import { useState } from "react";
+import useDeepCompareEffect from "use-deep-compare-effect";
import type {
SandboxEnvironment,
@@ -54,19 +54,12 @@ export type UseFiles = (props: SandpackProviderProps) => [
export const useFiles: UseFiles = (props) => {
const originalStateFromProps = getSandpackStateFromProps(props);
- const prevOriginalStateFromProps = useRef(originalStateFromProps);
const [state, setState] = useState(originalStateFromProps);
- const filesHaveChanged = !isEqual(
- prevOriginalStateFromProps.current,
- originalStateFromProps
- );
- if (filesHaveChanged) {
- setState(originalStateFromProps);
-
- prevOriginalStateFromProps.current = originalStateFromProps;
- }
+ useDeepCompareEffect(() => {
+ setState(getSandpackStateFromProps(props));
+ }, [props]);
const updateFile = (
pathOrFiles: string | SandpackFiles,
diff --git a/yarn.lock b/yarn.lock
index 169defc90..5f19e3257 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5016,18 +5016,6 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
-"@types/lodash.isequal@^4.5.2":
- version "4.5.6"
- resolved "https://registry.yarnpkg.com/@types/lodash.isequal/-/lodash.isequal-4.5.6.tgz#ff42a1b8e20caa59a97e446a77dc57db923bc02b"
- integrity sha512-Ww4UGSe3DmtvLLJm2F16hDwEQSv7U0Rr8SujLUA2wHI2D2dm8kPu6Et+/y303LfjTIwSBKXB/YTUcAKpem/XEg==
- dependencies:
- "@types/lodash" "*"
-
-"@types/lodash@*":
- version "4.14.182"
- resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
- integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
-
"@types/lodash@^4.14.167":
version "4.14.186"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.186.tgz#862e5514dd7bd66ada6c70ee5fce844b06c8ee97"
@@ -8234,7 +8222,7 @@ deprecation@^2.0.0, deprecation@^2.3.1:
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
-dequal@^2.0.0:
+dequal@^2.0.0, dequal@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
@@ -12845,11 +12833,6 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
-lodash.isequal@^4.5.0:
- version "4.5.0"
- resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
- integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
-
lodash.ismatch@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
@@ -18796,6 +18779,14 @@ use-clipboard-copy@^0.2.0:
dependencies:
clipboard-copy "^3.0.0"
+use-deep-compare-effect@1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/use-deep-compare-effect/-/use-deep-compare-effect-1.8.1.tgz#ef0ce3b3271edb801da1ec23bf0754ef4189d0c6"
+ integrity sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ dequal "^2.0.2"
+
use-sync-external-store@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"