1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-12 02:03:59 +02:00

Change how Slate context updates and introduce useSlateSelection hook (#5041)

* Fix DOM selection sync when there are unexpected rerenders

* Create a useSlateSelection hook and expose it

* update docs

* add changeset

* Undo the useEffect change and add a useSlateValue method

* Use a version counter instead for SlateContext

* comment out layout effect prevention for now

* Undo useV comparison for now

* Change the changeset

* Fix lint

* Remove the useSlateValue hook

* remove some unused imports

* Add useSlateWithV to the docs

* fix changeset lint

* Change changeset to minor instead
This commit is contained in:
Bryan Haakman
2022-07-23 01:27:26 +02:00
committed by GitHub
parent 1cc0797f53
commit 9bc0b6132a
7 changed files with 64 additions and 8 deletions

View File

@@ -0,0 +1,7 @@
---
'slate-react': minor
---
- Introduces a `useSlateSelection` hook that triggers whenever the selection changes.
- This also changes the implementation of SlateContext to use an incrementing value instead of an array replace to trigger updates
- Introduces a `useSlateWithV` hook that includes the version counter which can be used to prevent re-renders

View File

@@ -96,10 +96,18 @@ Get the current `selected` state of an element.
Get the current editor object from the React context. Re-renders the context whenever changes occur in the editor. Get the current editor object from the React context. Re-renders the context whenever changes occur in the editor.
### `useSlateWithV`
The same as `useSlate()` but includes a version counter which you can use to prevent re-renders.
### `useSlateStatic` ### `useSlateStatic`
Get the current editor object from the React context. A version of useSlate that does not re-render the context. Previously called `useEditor`. Get the current editor object from the React context. A version of useSlate that does not re-render the context. Previously called `useEditor`.
### `useSlateSelection`
Get the current editor selection from the React context. Only re-renders when the selection changes.
## ReactEditor ## ReactEditor
A React and DOM-specific version of the `Editor` interface. All about translating between the DOM and Slate. A React and DOM-specific version of the `Editor` interface. All about translating between the DOM and Slate.

View File

@@ -150,7 +150,7 @@ export const Editable = (props: EditableProps) => {
[] []
) )
// Whenever the editor updates... // Whenever the editor updates, sync the DOM selection with the slate selection
useIsomorphicLayoutEffect(() => { useIsomorphicLayoutEffect(() => {
// Update element-related weak maps with the DOM element ref. // Update element-related weak maps with the DOM element ref.
let window let window

View File

@@ -3,7 +3,7 @@ import { Editor, Node, Descendant, Scrubber } from 'slate'
import { ReactEditor } from '../plugin/react-editor' import { ReactEditor } from '../plugin/react-editor'
import { FocusedContext } from '../hooks/use-focused' import { FocusedContext } from '../hooks/use-focused'
import { EditorContext } from '../hooks/use-slate-static' import { EditorContext } from '../hooks/use-slate-static'
import { SlateContext } from '../hooks/use-slate' import { SlateContext, SlateContextValue } from '../hooks/use-slate'
import { import {
getSelectorContext, getSelectorContext,
SlateSelectorContext, SlateSelectorContext,
@@ -26,7 +26,7 @@ export const Slate = (props: {
const { editor, children, onChange, value, ...rest } = props const { editor, children, onChange, value, ...rest } = props
const unmountRef = useRef(false) const unmountRef = useRef(false)
const [context, setContext] = React.useState<[ReactEditor]>(() => { const [context, setContext] = React.useState<SlateContextValue>(() => {
if (!Node.isNodeList(value)) { if (!Node.isNodeList(value)) {
throw new Error( throw new Error(
`[Slate] value is invalid! Expected a list of elements` + `[Slate] value is invalid! Expected a list of elements` +
@@ -41,7 +41,7 @@ export const Slate = (props: {
} }
editor.children = value editor.children = value
Object.assign(editor, rest) Object.assign(editor, rest)
return [editor] return { v: 0, editor }
}) })
const { const {
@@ -54,7 +54,10 @@ export const Slate = (props: {
onChange(editor.children) onChange(editor.children)
} }
setContext([editor]) setContext(prevContext => ({
v: prevContext.v + 1,
editor,
}))
handleSelectorChange(editor) handleSelectorChange(editor)
}, [onChange]) }, [onChange])

View File

@@ -0,0 +1,17 @@
import { BaseSelection, Range } from 'slate'
import { useSlateSelector } from './use-slate-selector'
/**
* Get the current slate selection.
* Only triggers a rerender when the selection actually changes
*/
export const useSlateSelection = () => {
return useSlateSelector(editor => editor.selection, isSelectionEqual)
}
const isSelectionEqual = (a: BaseSelection, b: BaseSelection) => {
if (!a && !b) return true
if (!a || !b) return false
return Range.equals(a, b)
}

View File

@@ -7,7 +7,15 @@ import { ReactEditor } from '../plugin/react-editor'
* context whenever changes occur. * context whenever changes occur.
*/ */
export const SlateContext = createContext<[ReactEditor] | null>(null) export interface SlateContextValue {
v: number
editor: ReactEditor
}
export const SlateContext = createContext<{
v: number
editor: ReactEditor
} | null>(null)
/** /**
* Get the current editor object from the React context. * Get the current editor object from the React context.
@@ -22,6 +30,18 @@ export const useSlate = (): Editor => {
) )
} }
const [editor] = context const { editor } = context
return editor return editor
} }
export const useSlateWithV = () => {
const context = useContext(SlateContext)
if (!context) {
throw new Error(
`The \`useSlate\` hook must be used inside the <Slate> component's context.`
)
}
return context
}

View File

@@ -23,8 +23,9 @@ export { useSlateStatic } from './hooks/use-slate-static'
export { useFocused } from './hooks/use-focused' export { useFocused } from './hooks/use-focused'
export { useReadOnly } from './hooks/use-read-only' export { useReadOnly } from './hooks/use-read-only'
export { useSelected } from './hooks/use-selected' export { useSelected } from './hooks/use-selected'
export { useSlate } from './hooks/use-slate' export { useSlate, useSlateWithV } from './hooks/use-slate'
export { useSlateSelector } from './hooks/use-slate-selector' export { useSlateSelector } from './hooks/use-slate-selector'
export { useSlateSelection } from './hooks/use-slate-selection'
// Plugin // Plugin
export { ReactEditor } from './plugin/react-editor' export { ReactEditor } from './plugin/react-editor'