tldraw/apps/examples/src/index.tsx
alex 2d2a7ea76f
Fix multiple editor instances issue (#4001)
React's strict mode runs effects twice on mount, but once it's done that
it'll go forward with the state from the first effect. For example, this
component:

```tsx
let nextId = 1
function Component() {
	const [state, setState] = useState(null)
	useEffect(() => {
		const id = nextId++
		console.log('set up', id)
		setState(id)
		return () => console.log('tear down', id)
	}, [])
	if (!state) return
	console.log('render', state)
}
```

Would log something like this when mounting for the first time:
- `set up 1`
- `tear down 1`
- `set up 2`
- `render 1`

For us, this is a problem: editor 2 is the version that's still running,
but editor 1 is getting used for render. React talks a bit about this
issue here: https://github.com/reactwg/react-18/discussions/19

The fix seems to be to keep the editor in a `useRef` instead of a
`useState`. We need the state to trigger re-renders though, so we sync
the ref into the state although we don't actually use the state value.

### Change Type


- [x] `sdk` — Changes the tldraw SDK
- [x] `bugfix` — Bug fix



### Release Notes

- Fix a bug causing text shape measurement to work incorrectly when
using react strict mode
2024-06-24 12:24:24 +00:00

92 lines
2.5 KiB
TypeScript

import { getAssetUrlsByMetaUrl } from '@tldraw/assets/urls'
import { createRoot } from 'react-dom/client'
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
import {
DefaultErrorFallback,
ErrorBoundary,
setDefaultEditorAssetUrls,
setDefaultUiAssetUrls,
} from 'tldraw'
import { ExamplePage } from './ExamplePage'
import { examples } from './examples'
import Develop from './misc/develop'
import EndToEnd from './misc/end-to-end'
// we use secret internal `setDefaultAssetUrls` functions to set these at the
// top-level so assets don't need to be passed down in every single example.
const assetUrls = getAssetUrlsByMetaUrl()
// eslint-disable-next-line local/no-at-internal
setDefaultEditorAssetUrls(assetUrls)
// eslint-disable-next-line local/no-at-internal
setDefaultUiAssetUrls(assetUrls)
const gettingStartedExamples = examples.find((e) => e.id === 'Getting started')
if (!gettingStartedExamples) throw new Error('Could not find getting started exmaples')
const basicExample = gettingStartedExamples.value.find((e) => e.title === 'Tldraw component')
if (!basicExample) throw new Error('Could not find initial example')
const router = createBrowserRouter([
{
path: '*',
lazy: async () => ({ element: <div>404</div> }),
},
{
path: '/',
lazy: async () => {
const Component = await basicExample.loadComponent()
return {
element: (
<ExamplePage example={basicExample}>
<Component />
</ExamplePage>
),
}
},
},
{
path: 'develop',
lazy: async () => ({ element: <Develop /> }),
},
{
path: 'end-to-end',
lazy: async () => ({ element: <EndToEnd /> }),
},
...examples.flatMap((exampleArray) =>
exampleArray.value.flatMap((example) => [
{
path: example.path,
lazy: async () => {
const Component = await example.loadComponent()
return {
element: (
<ExamplePage example={example}>
<Component />
</ExamplePage>
),
}
},
},
{
path: `${example.path}/full`,
lazy: async () => {
const Component = await example.loadComponent()
return {
element: <Component />,
}
},
},
])
),
])
document.addEventListener('DOMContentLoaded', () => {
const rootElement = document.getElementById('root')!
const root = createRoot(rootElement!)
root.render(
<ErrorBoundary
fallback={(error) => <DefaultErrorFallback error={error} />}
onError={(error) => console.error(error)}
>
<RouterProvider router={router} />
</ErrorBoundary>
)
})