Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import SimpleValues from './SimpleValues';
import SymbolKeys from './SymbolKeys';
import UseMemoCache from './UseMemoCache';
import UseEffectEvent from './UseEffectEvent';
import UseSyncExternalStore from './UseSyncExternalStore';

// TODO Add Immutable JS example

Expand All @@ -38,6 +39,7 @@ export default function InspectableElements(): React.Node {
<SymbolKeys />
<UseMemoCache />
<UseEffectEvent />
<UseSyncExternalStore />
</Fragment>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import * as React from 'react';

const {useState, useEffect, useSyncExternalStore} = React;

// Create a simple external store for demonstratio
function createStore<T>(initialValue: T): {
subscribe: (cb: () => void) => () => any,
getSnapshot: () => T,
setValue: (newValue: T) => void,
} {
let value = initialValue;
const subscribers = new Set<() => void>();

return {
subscribe(callback) {
subscribers.add(callback);
return () => subscribers.delete(callback);
},
getSnapshot() {
return value;
},
setValue(newValue) {
value = newValue;
subscribers.forEach(callback => callback());
},
};
}

const counterStore = createStore(0);
const themeStore = createStore('light');

export default function UseSyncExternalStore(): React.Node {
return (
<>
<h2>useSyncExternalStore()</h2>
<SingleHookCase />
<HookTreeCase />
<MultipleStoresCase />
</>
);
}

function SingleHookCase(): React.Node {
const count = useSyncExternalStore(
counterStore.subscribe,
counterStore.getSnapshot,
);

return (
<div>
<h3>Single hook case</h3>
<p>Count: {count}</p>
<button onClick={() => counterStore.setValue(count + 1)}>
Increment
</button>
<button onClick={() => counterStore.setValue(count - 1)}>
Decrement
</button>
</div>
);
}

function useCounter() {
const count = useSyncExternalStore(
counterStore.subscribe,
counterStore.getSnapshot,
);
const [localState, setLocalState] = useState(0);

useEffect(() => {
// Some effect
}, [count]);

return {count, localState, setLocalState};
}

function HookTreeCase(): React.Node {
const {count, localState, setLocalState} = useCounter();

return (
<div>
<h3>Hook tree case</h3>
<p>External count: {count}</p>
<p>Local state: {localState}</p>
<button onClick={() => counterStore.setValue(count + 1)}>
Increment External
</button>
<button onClick={() => setLocalState(localState + 1)}>
Increment Local
</button>
</div>
);
}

function useTheme() {
const theme = useSyncExternalStore(
themeStore.subscribe,
themeStore.getSnapshot,
);

return theme;
}

function MultipleStoresCase() {
const count = useSyncExternalStore(
counterStore.subscribe,
counterStore.getSnapshot,
);
const theme = useTheme();

return (
<div style={{background: theme === 'dark' ? '#333' : '#fff'}}>
<h3>Multiple stores case</h3>
<p>Count: {count}</p>
<p>Theme: {theme}</p>
<button
onClick={() =>
themeStore.setValue(theme === 'light' ? 'dark' : 'light')
}>
Toggle Theme
</button>
</div>
);
}
Loading