Skip to content

Commit

Permalink
Make onChange prop optional, update examples and docs to treat slate …
Browse files Browse the repository at this point in the history
…as uncontrolled (#4922)

* Make onChange prop optional, update examples and docs to treat slate as uncontrolled

* Add changeset
  • Loading branch information
BitPhinix committed Apr 3, 2022
1 parent 08d5a12 commit 9892cf0
Show file tree
Hide file tree
Showing 29 changed files with 207 additions and 246 deletions.
5 changes: 5 additions & 0 deletions .changeset/red-bottles-peel.md
@@ -0,0 +1,5 @@
---
'slate-react': patch
---

Make Slate component onChange optional
70 changes: 18 additions & 52 deletions docs/walkthroughs/01-installing-slate.md
Expand Up @@ -66,53 +66,23 @@ declare module 'slate' {
}
```

```typescript
// Also you must annotate `useState<Descendant[]>` and the editor's initial value.
const App = () => {
const initialValue: CustomElement[] = []
const [value, setValue] = useState<Descendant[]>(initialValue)
return (
<Slate value={value} onChange={setValue}>
...
</Slate>
)
}
```

Next we want to create state for `value`:

```jsx
const App = () => {
const [editor] = useState(() => withReact(createEditor()))

// Keep track of state for the value of the editor.
const [value, setValue] = useState([])
return null
}
```

Next up is to render a `<Slate>` context provider.

The provider component keeps track of your Slate editor, its plugins, its value, its selection, and any changes that occur. It **must** be rendered above any `<Editable>` components. But it can also provide the editor state to other components like toolbars, menus, etc. using the `useSlate` hook.

```jsx
const initialValue = []

const App = () => {
const [editor] = useState(() => withReact(createEditor()))
const [value, setValue] = useState([])
// Render the Slate context.
return (
<Slate
editor={editor}
value={value}
onChange={newValue => setValue(newValue)}
/>
)
return <Slate editor={editor} value={initialValue} />
}
```

You can think of the `<Slate>` component as providing a context to every component underneath it.

> As of v0.67 the Slate Provider's "value" prop is now only used as initial state for editor.children. If your code relies on replacing editor.children you should do so by replacing it directly instead of relying on the "value" prop to do this for you. See [Slate PR 4540](https://github.com/ianstormtaylor/slate/pull/4540) for a more in-depth discussion.
> Slate Provider's "value" prop is only used as initial state for editor.children. If your code relies on replacing editor.children you should do so by replacing it directly instead of relying on the "value" prop to do this for you. See [Slate PR 4540](https://github.com/ianstormtaylor/slate/pull/4540) for a more in-depth discussion.
This is a slightly different mental model than things like `<input>` or `<textarea>`, because richtext documents are more complex. You'll often want to include toolbars, or live previews, or other complex components next to your editable content.

Expand All @@ -121,16 +91,14 @@ By having a shared context, those other components can execute commands, query t
Okay, so the next step is to render the `<Editable>` component itself:

```jsx
const initialValue = []

const App = () => {
const [editor] = useState(() => withReact(createEditor()))
const [value, setValue] = useState([])
return (
// Add the editable component inside the context.
<Slate
editor={editor}
value={value}
onChange={newValue => setValue(newValue)}
>
<Slate editor={editor} value={initialValue}>
<Editable />
</Slate>
)
Expand All @@ -144,22 +112,20 @@ There's only one last step. So far we've been using an empty `[]` array as the i
The value is just plain JSON. Here's one containing a single paragraph block with some text in it:

```jsx
// Add the initial value.
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]

const App = () => {
const [editor] = useState(() => withReact(createEditor()))
// Add the initial value when setting up our state.
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const [editor] = useState(() => withReact(createEditor())
const [value, setValue] = useState()

return (
<Slate
editor={editor}
value={value}
onChange={newValue => setValue(newValue)}
>
<Slate editor={editor} value={initialValue}>
<Editable />
</Slate>
)
Expand Down
45 changes: 24 additions & 21 deletions docs/walkthroughs/02-adding-event-handlers.md
Expand Up @@ -9,17 +9,18 @@ Let's use the `onKeyDown` handler to change the editor's content when we press a
Here's our app from earlier:

```jsx
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]

const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])

return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
<Slate editor={editor} value={initialValue}>
<Editable />
</Slate>
)
Expand All @@ -29,17 +30,18 @@ const App = () => {
Now we add an `onKeyDown` handler:

```jsx
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]

const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])

return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
<Slate editor={editor} value={initialValue}>
<Editable
// Define a new handler which prints the key that was pressed.
onKeyDown={event => {
Expand All @@ -58,17 +60,18 @@ Now we want to make it actually change the content. For the purposes of our exam
Our `onKeyDown` handler might look like this:

```jsx
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]

const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])

return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
<Slate editor={editor} value={initialValue}>
<Editable
onKeyDown={event => {
if (event.key === '&') {
Expand Down
60 changes: 32 additions & 28 deletions docs/walkthroughs/03-defining-custom-elements.md
Expand Up @@ -7,17 +7,18 @@ But that's not all you can do. Slate lets you define any type of custom blocks y
We'll show you how. Let's start with our app from earlier:

```jsx
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]

const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])

return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
<Slate editor={editor} value={initialValue}>
<Editable
onKeyDown={event => {
if (event.key === '&') {
Expand Down Expand Up @@ -65,14 +66,15 @@ const DefaultElement = props => {
Now, let's add that renderer to our `Editor`:

```jsx
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]

const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])

// Define a rendering function based on the element passed to `props`. We use
// `useCallback` here to memoize the function for subsequent renders.
Expand All @@ -86,7 +88,7 @@ const App = () => {
}, [])

return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
<Slate editor={editor} value={initialValue}>
<Editable
// Pass in the `renderElement` function.
renderElement={renderElement}
Expand Down Expand Up @@ -120,14 +122,15 @@ Okay, but now we'll need a way for the user to actually turn a block into a code
// Import the `Editor` and `Transforms` helpers from Slate.
import { Editor, Transforms } from 'slate'

const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]

const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])

const renderElement = useCallback(props => {
switch (props.element.type) {
Expand All @@ -139,7 +142,7 @@ const App = () => {
}, [])

return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
<Slate editor={editor} value={initialValue}>
<Editable
renderElement={renderElement}
onKeyDown={event => {
Expand Down Expand Up @@ -177,14 +180,15 @@ Now, if you press ``Ctrl-``` the block your cursor is in should turn into a code
But we forgot one thing. When you hit ``Ctrl-``` again, it should change the code block back into a paragraph. To do that, we'll need to add a bit of logic to change the type we set based on whether any of the currently selected blocks are already a code block:

```jsx
const initialValue = [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]

const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])

const renderElement = useCallback(props => {
switch (props.element.type) {
Expand All @@ -196,7 +200,7 @@ const App = () => {
}, [])

return (
<Slate editor={editor} value={value} onChange={value => setValue(value)}>
<Slate editor={editor} value={initialValue}>
<Editable
renderElement={renderElement}
onKeyDown={event => {
Expand Down

0 comments on commit 9892cf0

Please sign in to comment.