Skip to content

Commit

Permalink
Change how Slate context updates and introduce useSlateSelection ho…
Browse files Browse the repository at this point in the history
…ok (#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
  • Loading branch information
bryanph authored Jul 22, 2022
1 parent 1cc0797 commit 9bc0b61
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 8 deletions.
7 changes: 7 additions & 0 deletions .changeset/angry-files-kick.md
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions docs/libraries/slate-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

### `useSlateWithV`

The same as `useSlate()` but includes a version counter which you can use to prevent re-renders.

### `useSlateStatic`

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

A React and DOM-specific version of the `Editor` interface. All about translating between the DOM and Slate.
Expand Down
2 changes: 1 addition & 1 deletion packages/slate-react/src/components/editable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
// Update element-related weak maps with the DOM element ref.
let window
Expand Down
11 changes: 7 additions & 4 deletions packages/slate-react/src/components/slate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Editor, Node, Descendant, Scrubber } from 'slate'
import { ReactEditor } from '../plugin/react-editor'
import { FocusedContext } from '../hooks/use-focused'
import { EditorContext } from '../hooks/use-slate-static'
import { SlateContext } from '../hooks/use-slate'
import { SlateContext, SlateContextValue } from '../hooks/use-slate'
import {
getSelectorContext,
SlateSelectorContext,
Expand All @@ -26,7 +26,7 @@ export const Slate = (props: {
const { editor, children, onChange, value, ...rest } = props
const unmountRef = useRef(false)

const [context, setContext] = React.useState<[ReactEditor]>(() => {
const [context, setContext] = React.useState<SlateContextValue>(() => {
if (!Node.isNodeList(value)) {
throw new Error(
`[Slate] value is invalid! Expected a list of elements` +
Expand All @@ -41,7 +41,7 @@ export const Slate = (props: {
}
editor.children = value
Object.assign(editor, rest)
return [editor]
return { v: 0, editor }
})

const {
Expand All @@ -54,7 +54,10 @@ export const Slate = (props: {
onChange(editor.children)
}

setContext([editor])
setContext(prevContext => ({
v: prevContext.v + 1,
editor,
}))
handleSelectorChange(editor)
}, [onChange])

Expand Down
17 changes: 17 additions & 0 deletions packages/slate-react/src/hooks/use-slate-selection.tsx
Original file line number Diff line number Diff line change
@@ -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)
}
24 changes: 22 additions & 2 deletions packages/slate-react/src/hooks/use-slate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ import { ReactEditor } from '../plugin/react-editor'
* 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.
Expand All @@ -22,6 +30,18 @@ export const useSlate = (): Editor => {
)
}

const [editor] = context
const { editor } = context
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
}
3 changes: 2 additions & 1 deletion packages/slate-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ export { useSlateStatic } from './hooks/use-slate-static'
export { useFocused } from './hooks/use-focused'
export { useReadOnly } from './hooks/use-read-only'
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 { useSlateSelection } from './hooks/use-slate-selection'

// Plugin
export { ReactEditor } from './plugin/react-editor'
Expand Down

0 comments on commit 9bc0b61

Please sign in to comment.