slate
slate copied to clipboard
Transforms.setNodes causes nodes to disappear
Problem
I'm using Transforms.setNodes
to update some nodes (specifically link elements). Unfortunately, this results in some weird behavior where the nodes are completely removed instead of being updated.
Codesandbox: https://codesandbox.io/s/gifted-nightingale-shm5b
Basically, I'm trying to modify some metadata inside a link element by using Transforms.setNodes
. Unexpectedly, this causes the link to disappear entirely.
The starting editor value is "This is an Example link in a paragraph", which then turns into "This is an in a paragraph" once the transform runs. This happens both when using slate-react (as demonstrated in Editor.js
) as well as headlessly (as demonstrated in Headless.js
).
Solution
Transforms.setNodes
should not delete nodes. It should only update the nodes' properties.
Problem I'm writing a note-taking app where, if an action is taken in one note, I want several other notes to be updated at the same time. It's not necessary to render these other notes, but I want to be able to use the Slate API to update them. So, my question is: is it possible to edit Slate content without rendering it?
I've tried to do this with code similar to the following:
const editor = createEditor(); editor.children = note.content;
And then using
Transforms.setNodes
to update some nodes in the other notes. Unfortunately, this results in some weird behavior where the nodes are completely removed instead of being updated.I've tested out my logic when the editor is actually being rendered, and it seems to work fine. So it appears to me as if the Slate API depends on the editor being rendered.
Solution I'd like to be able to manipulate nodes without rendering the editor (
Slate
/Editable
).Alternatives I've considered manipulating the JSON directly, but it's non-ideal because then I would lose many of the protections that the Slate API has built in.
I could also render the editor invisibly, but that's kind of hacky.
Context Speaking from a high-level, I think it makes sense for the API methods in Slate core to be able to be used even if the Slate editor isn't being rendered, since the editor value is just a JSON object. Also, this would help to ensure that
slate
andslate-react
are correctly abstracted from each other.
Hello @churichard
This should absolutely be doable without slate-react
, can you provide a codesandbox to reproduce the issue?
After some further investigation, I think I've encountered a bug with Transforms.setNodes
that happens both headlessly and non-headlessly.
Heres's a codesandbox that demonstrates this issue: https://codesandbox.io/s/gifted-nightingale-shm5b
Basically, I'm trying to modify some metadata inside a link element by using Transforms.setNodes
. Unexpectedly, this causes the link to disappear entirely.
The starting editor value is "This is an Example link in a paragraph", which then turns into "This is an in a paragraph" once the transform runs. This happens both when using slate-react (as demonstrated in Editor.js) as well as headlessly (as demonstrated in Headless.js).
Hi @churichard did you solve this? I'm having an equivalent problem with image-type leaf nodes.
Unfortunately not. I opted to modify the JSON manually, which works fine for the older version of Slate that I'm on.
My best guess is that the thing calling setNodes needs to get wrapped in a withoutNormalizing call, and the current approach seen here is likely triggering a normalization which is removing the node when its stuck in an intermediate state. That said, I'd need to look more closely at the specific example.
Thanks for the input @dylans but I'm afraid that's not it.
Strangely when I perform the same .setNode
on an element which is at index 0
of children
, it's OK.
When it is at index 1
, it vanishes.
Tricky thing is I'm stumped how to debug it...
Update:
I fixed my problem by explicitly returning true
from editor.isInline
for all images.
When Slate treated them as block void elements, I had the behaviour described above. Now when Slate treats them as inline void elements, it works as intended.
I can't claim to understand why there was inconsistency, but this fix is good enough for what I'm doing right now.
Hi,
I'm having the same issue with nested lists (where a <li has a nested <ul with subsequent <li children). When selecting the base/parent <li and attempting a setNodes operation, it deletes the child list. When selecting a child <ul/<li, it deletes the child list but keeps the parent.
Any idea what the hell is going on here?
Thanks!
Hello everyone. I have not the same issue but related to changing properties.
My initial value is
[
{
"type": "paragraph",
"children": [
{
"type": "company",
"children": [
{
"text": "My Company"
}
]
}
]
}
]
Each time when the Editor is loaded, I want to replace initial value of 'My Company' with the current value (e.g 'My Updated Company'). My approach is:
case 'company':
Transforms.setNodes(
editor,
{ type: 'customType' }, // updated successfully from "type": "company" to "type": "customType"
{ children: [{ text: 'My Updated Company' }] } // ignored
{
at: [],
match: (n) => SlateElement.isElement(n) && n.type === 'company'
}
);
return <Company {...props} />;
Any property I try to update is okay except children
that is ignored. Is there any way to update children[0].text
?
I was having this error trying to apply bold text in the same paragraph as a link, and was able to solve this the same way as @marcdavi-es.
I defined a function withInlines
:
function withInlines(editor: Editor) {
editor.isInline = (element) => element.type === 'link';
return editor;
}
Then applied it to the editor creation:
const [editor] = useState(() => withInlines(withReact(withHistory(createEditor()))));
I also saw that is the exact way that is done in the inlines example
Hello everyone. I have not the same issue but related to changing properties.
My initial value is
[ { "type": "paragraph", "children": [ { "type": "company", "children": [ { "text": "My Company" } ] } ] } ]
Each time when the Editor is loaded, I want to replace initial value of 'My Company' with the current value (e.g 'My Updated Company'). My approach is:
case 'company': Transforms.setNodes( editor, { type: 'customType' }, // updated successfully from "type": "company" to "type": "customType" { children: [{ text: 'My Updated Company' }] } // ignored { at: [], match: (n) => SlateElement.isElement(n) && n.type === 'company' } ); return <Company {...props} />;
Any property I try to update is okay except
children
that is ignored. Is there any way to updatechildren[0].text
?
function getCurrentNode(editor: Editor, type: CustomElement['type']) {
const [match] = Editor.nodes(editor, {
match: n => !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === type,
})
return match
}
const [, path] = getCurrentNode(editor, 'link') ?? []
if (path) {
Transforms.removeNodes(editor, { at: path })
Transforms.insertNodes(editor, link, { at: path })
}
I handle this by removing the original element and inserting the new one at the same time
Hello everyone. I have not the same issue but related to changing properties. My initial value is
[ { "type": "paragraph", "children": [ { "type": "company", "children": [ { "text": "My Company" } ] } ] } ]
Each time when the Editor is loaded, I want to replace initial value of 'My Company' with the current value (e.g 'My Updated Company'). My approach is:
case 'company': Transforms.setNodes( editor, { type: 'customType' }, // updated successfully from "type": "company" to "type": "customType" { children: [{ text: 'My Updated Company' }] } // ignored { at: [], match: (n) => SlateElement.isElement(n) && n.type === 'company' } ); return <Company {...props} />;
Any property I try to update is okay except
children
that is ignored. Is there any way to updatechildren[0].text
?function getCurrentNode(editor: Editor, type: CustomElement['type']) { const [match] = Editor.nodes(editor, { match: n => !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === type, }) return match } const [, path] = getCurrentNode(editor, 'link') ?? [] if (path) { Transforms.removeNodes(editor, { at: path }) Transforms.insertNodes(editor, link, { at: path }) }
I handle this by removing the original element and inserting the new one at the same time
@SCWR , this worked for me, but fails in a particular scenario. Let's assume, I have a paragraph and in between the paragraph there is a link element. I want to replace the link element's text content with a custom text. but for some reason the new link node is getting attached at the end of the paragraph instead of its actual place with the code.
Transforms.removeNodes(editor, {
at: path
});
Transforms.insertNodes(editor, [{
type: ELEMENT_LINK,
isUnfurled: true,
url: urlValue,
children: [
{
text: resolvedName
}
]
}],
{
at: path
});