6

How to easily set entire SlateJS editor value using React onEffect hook?

The initial editor value is set when creating the useState hook, however, I want to set a new initial value afterwards.

Currently, we can do that by deleting all elements with Transform and then inserting new elements, but is an easier way, like override all? I can't seem to find it in the SlateJS docs.

Saving to database slatejs example Don't work, but is how my setup functions

const App = () => {
  const editor = useMemo(() => withReact(createEditor()), [])
  // Update the initial content to be pulled from Local Storage if it exists.
  const [value, setValue] = useState(
    [
      {
        type: 'paragraph',
        children: [{ text: 'A line of text in a paragraph.' }],
      },
    ]
  )

  useEffect(() => {
    // Get saved SlateJS text from local storage
    const savedSlateTextData = getSavedSlateJSData(localStorage)

    // In theory, this should set the SlateJS values to the loaded data
    // In practice, I think because the editor is immutable outside transform, it don't work 
    setValue(savedSlateTextData)
  }, [])

  return (
    <Slate
      editor={editor}
      value={value}
      onChange={value => {
        setValue(value)
        const isAstChange = editor.operations.some(
          op => 'set_selection' !== op.type
        )
        if (isAstChange) {
          // Save the value to Local Storage.
          const content = JSON.stringify(value)
          localStorage.setItem('content', content)
        }
      }}
    >
      <Editable />
    </Slate>
  )
}
wanna_coder101
  • 508
  • 5
  • 19

3 Answers3

6

This is the cleanest way I could think of using what's available via slate

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

const [value, setValue] = useState(INITIAL_SLATE_VALUE)

// Delete all entries leaving 1 empty node
Transforms.delete(editor, {
   at: {
      anchor: Editor.start(editor, []),
      focus: Editor.end(editor, []),
   },
})

// Removes empty node
Transforms.removeNodes(editor, {
   at: [0],
})

// Insert array of children nodes
Transforms.insertNodes(
   editor,
   value
)
GCQ
  • 190
  • 3
  • 11
  • 1
    In my own project, I didn't always have a node to delete before the insertion. I added `if (editor.children.length > 0) {...` before `Transforms.delete` and `Transforms.removeNodes`. (But this was a fantastic solution to my problem! Thank you!) – D. Hammaker Sep 30 '22 at 01:58
4

Don't think what I want is possible — you need to delete all the nodes and then insert new ones.

Not sure if you could use match instead of a bunch of for loops, perhaps.

  // Get initial total nodes to prevent deleting affecting the loop
  let totalNodes = editor.children.length;

  // No saved content, don't delete anything to prevent errors
  if (savedSlateJSContent.length <= 0) return;

  // Remove every node except the last one
  // Otherwise SlateJS will return error as there's no content
  for (let i = 0; i < totalNodes - 1; i++) {
      console.log(i)
      Transforms.removeNodes(editor, {
          at: [totalNodes-i-1],
      });
  }

  // Add content to SlateJS
  for (const value of savedSlateJSContent ) {
      Transforms.insertNodes(editor, value, {
          at: [editor.children.length],
      });
  }

  // Remove the last node that was leftover from before
  Transforms.removeNodes(editor, {
      at: [0],
  });
wanna_coder101
  • 508
  • 5
  • 19
0

An alternative is to just reinstantiate a new Slate editor when initialValue changes:

const App = () => {
  // OLD way to do async stuff in React
  const [initialValue, setInitialValue] = useState([
      {
        type: 'paragraph',
        children: [{ text: 'A line of text in a paragraph.' }],
      },
    ])
  useEffect(async () => {
    setValue(await getSavedSlateJSData(localStorage))
  }, [])

  // NEW way
  // const initialValue = use(getSavedSlateJSData(localStorage))

  return <MyEditor initialValue={initialValue} />
  // (alternatively don't render editor until value is loaded)
}

const MyEditor = ({ initialValue }) => {
  const [value, setValue] = useState(initialValue)
  const editor = useMemo(() => withReact(createEditor()), [])
  return (
    <Slate
      editor={editor}
      value={value}
      onChange={value => {
        setValue(value)
        const isAstChange = editor.operations.some(
          op => 'set_selection' !== op.type
        )
        if (isAstChange) {
          // Save the value to Local Storage.
          const content = JSON.stringify(value)
          localStorage.setItem('content', content)
        }
      }}
    >
      <Editable />
    </Slate>
  )
}
beorn
  • 11
  • 3