Fix editor remounting when camera options change (#4089)
Currently, the editor gets recreated whenever the camera options (or several other props that are only relevant at initialisation time) get changed. This diff makes it so that: - init-only props are kept in a ref so they don't invalidate the editor (but are used when the editor _does_ get recreated) - camera options are kept up to date in a separate effect ### Change type - [x] `bugfix` ### Release notes Fix an issue where changing `cameraOptions` via react would cause the entire editor to re-render
This commit is contained in:
parent
a9ca00c04c
commit
cf32a09221
2 changed files with 70 additions and 36 deletions
|
@ -361,44 +361,60 @@ function TldrawEditorWithReadyStore({
|
||||||
setRenderEditor(editor)
|
setRenderEditor(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [initialAutoFocus] = useState(autoFocus)
|
// props in this ref can be changed without causing the editor to be recreated.
|
||||||
|
const editorOptionsRef = useRef({
|
||||||
useLayoutEffect(() => {
|
// for these, it's because they're only used when the editor first mounts:
|
||||||
const editor = new Editor({
|
autoFocus,
|
||||||
store,
|
|
||||||
shapeUtils,
|
|
||||||
bindingUtils,
|
|
||||||
tools,
|
|
||||||
getContainer: () => container,
|
|
||||||
user,
|
|
||||||
initialState,
|
|
||||||
autoFocus: initialAutoFocus,
|
|
||||||
inferDarkMode,
|
|
||||||
cameraOptions,
|
|
||||||
assetOptions,
|
|
||||||
options,
|
|
||||||
})
|
|
||||||
|
|
||||||
editorRef.current = editor
|
|
||||||
setRenderEditor(editor)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
editor.dispose()
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
container,
|
|
||||||
shapeUtils,
|
|
||||||
bindingUtils,
|
|
||||||
tools,
|
|
||||||
store,
|
|
||||||
user,
|
|
||||||
initialState,
|
|
||||||
initialAutoFocus,
|
|
||||||
inferDarkMode,
|
inferDarkMode,
|
||||||
|
initialState,
|
||||||
|
|
||||||
|
// for these, it's because we keep them up to date in a separate effect:
|
||||||
cameraOptions,
|
cameraOptions,
|
||||||
assetOptions,
|
})
|
||||||
options,
|
useLayoutEffect(() => {
|
||||||
])
|
editorOptionsRef.current = {
|
||||||
|
autoFocus,
|
||||||
|
inferDarkMode,
|
||||||
|
initialState,
|
||||||
|
cameraOptions,
|
||||||
|
}
|
||||||
|
}, [autoFocus, inferDarkMode, initialState, cameraOptions])
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
const { autoFocus, inferDarkMode, initialState, cameraOptions } = editorOptionsRef.current
|
||||||
|
const editor = new Editor({
|
||||||
|
store,
|
||||||
|
shapeUtils,
|
||||||
|
bindingUtils,
|
||||||
|
tools,
|
||||||
|
getContainer: () => container,
|
||||||
|
user,
|
||||||
|
initialState,
|
||||||
|
autoFocus,
|
||||||
|
inferDarkMode,
|
||||||
|
cameraOptions,
|
||||||
|
assetOptions,
|
||||||
|
options,
|
||||||
|
})
|
||||||
|
|
||||||
|
editorRef.current = editor
|
||||||
|
setRenderEditor(editor)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
editor.dispose()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// if any of these change, we need to recreate the editor.
|
||||||
|
[assetOptions, bindingUtils, container, options, shapeUtils, store, tools, user]
|
||||||
|
)
|
||||||
|
|
||||||
|
// keep the editor up to date with the latest camera options
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (editor && cameraOptions) {
|
||||||
|
editor.setCameraOptions(cameraOptions)
|
||||||
|
}
|
||||||
|
}, [editor, cameraOptions])
|
||||||
|
|
||||||
const crashingError = useSyncExternalStore(
|
const crashingError = useSyncExternalStore(
|
||||||
useCallback(
|
useCallback(
|
||||||
|
|
|
@ -226,6 +226,24 @@ describe('<TldrawEditor />', () => {
|
||||||
// but strict mode will cause onMount to be called twice
|
// but strict mode will cause onMount to be called twice
|
||||||
expect(onMount).toHaveBeenCalledTimes(2)
|
expect(onMount).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('allows updating camera options without re-creating the editor', async () => {
|
||||||
|
const editors: Editor[] = []
|
||||||
|
const onMount = jest.fn((editor: Editor) => {
|
||||||
|
if (!editors.includes(editor)) editors.push(editor)
|
||||||
|
})
|
||||||
|
|
||||||
|
const renderer = await renderTldrawComponent(<TldrawEditor onMount={onMount} />, {
|
||||||
|
waitForPatterns: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(editors.length).toBe(1)
|
||||||
|
expect(editors[0].getCameraOptions().isLocked).toBe(false)
|
||||||
|
|
||||||
|
renderer.rerender(<TldrawEditor onMount={onMount} cameraOptions={{ isLocked: true }} />)
|
||||||
|
expect(editors.length).toBe(1)
|
||||||
|
expect(editors[0].getCameraOptions().isLocked).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Custom shapes', () => {
|
describe('Custom shapes', () => {
|
||||||
|
|
Loading…
Reference in a new issue