Slate Cloud
The Slate Documentation on Saving to a Database takes the Editor value
and saves that value which is plain JSON to a database.
Slate Cloud supports asynchronous uploads which allows users to keep editing while files are still uploading which is a useful feature; however, one side effect of this is that you can't just save the value
because there may be unfinished uploads in the Slate document.
Slate Cloud includes functions which help you get a valid value
from Slate.
With Slate Cloud there are two methods which return a valid value
which you can save to a database:
editor.cloud.normalize
: This returns the Slate value
with all unfinished uploads removed.
editor.cloud.save
: This is an async
function (i.e. it returns a Promise
) that waits until all uploads are finished before returning the Slate value
. It also has a timeout
option to prevent a save from taking too long. If the timeout
is reached, it returns a valid version of the document with any unfinished uploads removed.
If you do not use one of these two methods and instead just save Slate's
value
, then after you open the document again, the images and attachments will be invalid. This is because in addition to making sure the document is in a valid state, it replaces theid
which is a non-sensical reference in a lookup like#i3h54
to the actualurl
of the uploaded file likehttps://files.portive.com/f/demo/oqcjnuoy7w65ltojqkwdx--508x362.png
.
editor.cloud.normalize
This is a good way to get Slate's value
for saving when you don't want to wait for uploads to finish.
The normalize
method returns the Slate Editor's value with any elements that haven't finished uploading removed.
This is useful in a scenario where you want a snapshot of the document right now like if you want to save a draft of the document.
import { useCallback, useState } from "react"import { createEditor } from "slate"import { withHistory } from "slate-history"import { Editable, Slate, withReact } from "slate-react"import { withCloud } from "slate-cloud"import { CloudComponents } from "slate-cloud/cloud-components"
// ✅ Add `CloudComponents.withRenderElement` plugin on `renderElement`const renderElement = CloudComponents.withRenderElement((props) => { const { element } = props if (element.type === "paragraph") { return <p {...props.attributes}>{props.children}</p> } throw new Error(`Unhandled element type ${element.type}`)})
export default function Page() { const [editor] = useState(() => { const basicEditor = withHistory(withReact(createEditor())) // ✅ Add `withCloud` plugin on `Editor` object to enable uploads const cloudEditor = withCloud(basicEditor, { apiKey: "MY_API_KEY", }) // ✅ Add `CloudComponents.withEditor` plugin on `Editor` object CloudComponents.withEditor(cloudEditor) return cloudEditor })
const normalize = useCallback(() => { // get the `editor.value` with incomplete uploads removed const value = editor.cloud.normalize() console.log(value) }, [editor])
return ( <Slate editor={editor} value={[{ type: "paragraph", children: [{ text: "Hello World" }] }]} > <button onClick={normalize}>Normalize</button> <Editable renderElement={renderElement} // ✅ Add `editor.cloud.handlePaste` to `Editable onPaste` onPaste={editor.cloud.handlePaste} // ✅ Add `editor.cloud.handleDrop` to `Editable onDrop` onDrop={editor.cloud.handleDrop} /> </Slate> )}
editor.cloud.save
This is a way to get Slate's value
when you want to wait for uploads to complete first. Call await editor.cloud.save
which returns an object that contains the document value
.
function save(options?: SaveOptions) => Promise<SaveResult>
type SaveOptions = { maxTimeooutInMs?: number }
type SaveResult =
| { status: "timeout"; value: Descendant[]; finishes: Promise<Upload>[] }
| { status: "complete"; value: Descendant[] }
The method takes an optional { maxTimeoutInMs: number }
option. If the files aren't finished uploading by the timeout, a normalized document value will be return with the unfinished Elements removed.
Even though some incomplete elements are remove, the document value is in a valid state.
🌞 Note that when a normalized document is returned, it won't remove the Elements from the Editor. Any uploads will continue uploading after
save
is called.
import { useCallback, useState } from "react"import { createEditor } from "slate"import { withHistory } from "slate-history"import { Editable, Slate, withReact } from "slate-react"import { withCloud } from "slate-cloud"import { CloudComponents } from "slate-cloud/cloud-components"
// ✅ Add `CloudComponents.withRenderElement` plugin on `renderElement`const renderElement = CloudComponents.withRenderElement((props) => { const { element } = props if (element.type === "paragraph") { return <p {...props.attributes}>{props.children}</p> } throw new Error(`Unhandled element type ${element.type}`)})
export default function Page() { const [editor] = useState(() => { const basicEditor = withHistory(withReact(createEditor())) // ✅ Add `withCloud` plugin on `Editor` object to enable uploads const cloudEditor = withCloud(basicEditor, { apiKey: "MY_API_KEY", }) // ✅ Add `CloudComponents.withEditor` plugin on `Editor` object CloudComponents.withEditor(cloudEditor) return cloudEditor })
const normalize = useCallback(async () => { // waits for up to 10s for uploads to finish and returns a result object const result = await editor.cloud.save({ maxTimeoutInMs: 10000 }) console.log(result.value) }, [editor])
return ( <Slate editor={editor} value={[{ type: "paragraph", children: [{ text: "Hello World" }] }]} > <button onClick={normalize}>Normalize</button> <Editable renderElement={renderElement} // ✅ Add `editor.cloud.handlePaste` to `Editable onPaste` onPaste={editor.cloud.handlePaste} // ✅ Add `editor.cloud.handleDrop` to `Editable onDrop` onDrop={editor.cloud.handleDrop} /> </Slate> )}
We also recommend putting the editor into readOnly
mode so the user can't editing while a save is in progress.