diff --git a/apps/examples/src/examples/state-store/README.md b/apps/examples/src/examples/state-store/README.md new file mode 100644 index 000000000..6ccf19926 --- /dev/null +++ b/apps/examples/src/examples/state-store/README.md @@ -0,0 +1,12 @@ +--- +title: State and store +component: ./StateStoreExample.tsx +category: basic +priority: 3 +keywords: [signia, state, store, side, effects, subscribe, track] +--- + +Tldraw uses signals to manage its state and store. You can subscribe to +values in the store and run side effects when they change. + +--- diff --git a/apps/examples/src/examples/state-store/StateStoreExample.tsx b/apps/examples/src/examples/state-store/StateStoreExample.tsx new file mode 100644 index 000000000..fa3516f18 --- /dev/null +++ b/apps/examples/src/examples/state-store/StateStoreExample.tsx @@ -0,0 +1,88 @@ +import { TLComponents, Tldraw, track, useEditor, useReactor, useValue } from 'tldraw' +import 'tldraw/tldraw.css' + +// [1] +const InfoPanel = track(() => { + const editor = useEditor() + const tool = editor.getCurrentToolId() + const zoom = editor.getZoomLevel().toFixed(2) + useReactor( + 'change title', + () => { + const shapes = editor.getCurrentPageShapes() + document.title = `shapes: ${shapes.length}` + }, + [editor] + ) + return ( +
+
tool: {tool}
+
zoom: {zoom}
+
+ ) +}) + +// [2] +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function AlternativeInfoPanel() { + const editor = useEditor() + const tool = useValue( + 'current tool', + () => { + if (!editor) throw new Error('No editor') + return `Current Tool: ${editor.getCurrentToolId()}` + }, + [editor] + ) + const zoom = useValue( + 'zoom', + () => { + if (!editor) throw new Error('No editor') + return `Zoom Level: ${editor.getZoomLevel().toFixed(2)}` + }, + [editor] + ) + + return ( +
+
{tool}
+
{zoom}
+
+ ) +} + +const components: TLComponents = { + SharePanel: InfoPanel, +} + +export default function StateStoreExample() { + return ( +
+ +
+ ) +} + +/* + +Tldraw uses signals to manage its state and store. You can subscribe to values in the store +and run side effects when they change. + +[1] + Our InfoPanel component will display above the style panel. We want it to show the current + selected tool and zoom level of the editor. In order to make sure it displays up-to-date + information, we can wrap the component in the track function. This will track any signals + used in the component and re-render it when they change. + + We also use the useReactor hook to update the document title with the number of shapes. This + side effect will run whenever the shapes on the page change. We pass the editor as a + dependency to the useReactor hook so it will always have the latest editor instance. + useQuickReactor runs immediately, whereas useReactor runs on the next animation frame. + + Check out the Fog of War example to see useReactor in action. + +[2] + We can also use the useValue hook to subscribe to a value in the store. You can pass it a + value or a function. Functions will be memoized and only re-run when the dependencies change. + +*/