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 theidwhich is a non-sensical reference in a lookup like#i3h54to the actualurlof the uploaded file likehttps://files.portive.com/f/demo/oqcjnuoy7w65ltojqkwdx--508x362.png.
editor.cloud.normalizeThis 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.saveThis 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
saveis 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.