Slate: Normalization for initial value

Created on 24 Jan 2020  路  8Comments  路  Source: ianstormtaylor/slate

Do you want to request a _feature_ or report a _bug_?

feature

What's the current behavior?

1) Value is set on editor
2) Only when the user types something into the editor, the editor.normalizeNode is called for that specific node (and it's parent nodes)
3) Other nodes that have not been touched stay invalid until e.g. the text of that node is edited

Slate: 0.57.1
Browser: Chrome / Safari / Firefox / Edge
OS: Mac / Windows / Linux / iOS / Android

What's the expected behavior?

There are multiple solutions. I currently have 2 in mind:
1) All nodes are normalized once after the value has been set initially
2) A callback is exposed (e.g. onValueUpdated) where we could add some logic to run Editor.normalize(editor) everytime the value has been updated from outside (e.g. coming from a DB)

feature

Most helpful comment

After running into other issues with the above workarounds I finally ended up with this:

1) I have my editor component that receives the value as prop from outside
2) I store the "internal" slate value in a react state within my editor component
3) I use useEffect hook to update the internal slate value with the incoming external value

Now here comes the magic part. Instead of just setting the external value on the internal state I use this:

React.useEffect(() => {
    // Slate throws an error if the value on the initial render is invalid
    // so we directly set the value on the editor in order
    // to be able to trigger normalization on the initial value before rendering
    editor.children = externalValue
    Editor.normalize(editor, { force: true })
    // We set the internal value so that the rendering can take over from here
    setInternalSlateValue(editor.children)
}, [externalValue])

All 8 comments

What is the workaround here? Have you found a good way to trigger normalization on the entire document?

Is there any function in the Slate API which can normalize a Slate document programmatically?

I'm working on migrating documents from a C# editor to Slate documents and would like to normalize them in a final step.

I guess a function like that would meet your needs as well @aunger , wouldn't it?

What is the workaround here? Have you found a good way to trigger normalization on the entire document?

I'm currently waiting for the initialization and then make a change in every first-level child node and immediately undo that action.

// The setTimeout might not be necessary in your setup, but it is for mine.
setTimeout(() => {
  editor.children.forEach((_child, index) => {
    Transforms.insertText(editor, '_', { at: [index] })
    editor.undo()
  })
}, 1)

This feature would be very welcome.

I'm working on a project where we used to edit content with bbcode (yeah it's waaaay too old-school) and we are now migrating it to slate. I'm parsing the HTML output of our bbcode thus the initialValue doesn't comes form slate so it may require so normalization at initialization.

I actually do some pre-normalization on the generated HTML but it's only for the parser to correctly understand what is going on, then it still could yield an incorrect json tree.

@paullaffitte we're doing the exact same thing except that we migrate from TextControl HTML export to slate. I think migrations like that are a pretty common use case.

We'd also really appreciate this feature.

That's odd, I've just find an issue about disabling normalization at initialization from almost 2 years ago.. Is this feature still existing but maybe not documented? Or has it been remove since then?

I ran into some issues with @JonathanWbn 's workaround where a concurrently run value update collided with the editor.undo() call.
So I ended up triggering the normalization with this workaround:

editor.children.forEach((child, index) => {
  Transforms.setNodes(editor, [child], { at: [index] })
})

After running into other issues with the above workarounds I finally ended up with this:

1) I have my editor component that receives the value as prop from outside
2) I store the "internal" slate value in a react state within my editor component
3) I use useEffect hook to update the internal slate value with the incoming external value

Now here comes the magic part. Instead of just setting the external value on the internal state I use this:

React.useEffect(() => {
    // Slate throws an error if the value on the initial render is invalid
    // so we directly set the value on the editor in order
    // to be able to trigger normalization on the initial value before rendering
    editor.children = externalValue
    Editor.normalize(editor, { force: true })
    // We set the internal value so that the rendering can take over from here
    setInternalSlateValue(editor.children)
}, [externalValue])
Was this page helpful?
0 / 5 - 0 ratings