Bug: Cannot update slate state externally
Description
The PR https://github.com/ianstormtaylor/slate/pull/4540 removed the ability to update the slate state using the value prop. As a result, we cannot inject externally changed state anymore.
If there is another way to update the state externally, then the documentation should be updated as well. https://docs.slatejs.org/walkthroughs/06-saving-to-a-database
Environment
- Slate Version: "^0.67.0"
- Operating System: Windows
- Browser: Firefox
Ouch, we got bitten hard by this. Downgraded for now.
@Ghost---Shadow if you look at what was changed (https://github.com/ianstormtaylor/slate/pull/4540/files), you can easily get the same behaviour by setting a key property on the Slate element and incrementing whenever your state changes.
@Ghost---Shadow if you look at what was changed (https://github.com/ianstormtaylor/slate/pull/4540/files), you can easily get the same behaviour by setting a key property on the Slate element and incrementing whenever your state changes.
Can you elaborate a little bit more with the key hook code sample. Wouldn't it cause some re-render issues of entire slate component while changing the value or visually the text.
I'm ok with the change but little bit lose the direction to achieve the similar behavior in a proper way. Just roll back to last version until the doc fixed,
I am very aware of any slate "breaking" change but it's a still a powerful blackbox machine to me, still on basic/user level and try to understand the internal logic.
A temporary solution: update editor.children instead of value, as specified in [email protected] release notes.
This is a hack, not a solution. If this is the official way of updating the state, then the documentation should be updated as well.
It works, but I agree, it's a hack. Setting editor.children directly also requires manual fixing of selection, because selected range remains the same even if some new nodes are added above the selection.
It works, but I agree, it's a hack. Setting editor.children directly also requires manual fixing of selection, because selected range remains the same even if some new nodes are added above the selection.
It does not seem like a hack based on the PR discussion. It's just not a React way, and not necessary to be a React way. but selection is definitively a problem. And it's confusing comparing to the past implementation.
It took me several days to find out that changes in #3216 were silently reversed in #4540. It would be great to reflect the changes not only in docs as others say above, but also in API (eg revert value to defaultValue or initialValue) as the value prop implies controlled behavior.
It‘s possible that out there are many others struggling to find out what went wrong (in our case it wasn’t an upgrade going bad — that would make me go for changelists sooner, but documentation being out of date and API pretending that everything is okay).
(Hence, I’d propose relabeling this as an improvement and changing the name of the prop — such change would highlight its breaking nature, as the difference between un- and controlled components is night and day.)
A solution: update editor.children instead of value, as specified in [email protected] release notes.
It's really not a react way.
It's really not a react way.
Of course it's not. I hope somebody will make a refactoring.
It's really not a react way.
Of course it's not. I hope somebody will make a refactoring.
slate was claimed to be built on top of React in github main page, but we have slate react dedicated for React logic purpose if I'm not mistaken with the major functionalities. So does the same claim applies on slate core(slate)?
Now the problem is that if this PR #4540 is merged intentionally, does it mean the slate core actually does not rely on react?
which is extremely important for the future release.
as well as my questionable past statement .
It's just not a React way, and not necessary to be a React way. ?
Strictly speaking: controlled component vs uncontrolled component instead of react way.
Indeed, there are some silent disagreements contributors were unware of.
I guess it's good to ask @bryanph for clarification and his plan.
slatewas claimed to be built on top ofReactin github main page, but we haveslate reactdedicated for React logic purpose if I'm not mistaken with the major functionalities. So does the same claim applies onslate core(slate)?Now the problem is that if this PR #4540 is merged intentionally, does it mean the
slate coreactually does not rely onreact? which is extremely important for the future release. as well as my questionable past statement .
Yes, slate core doesn't rely on react by design. slate-react modifies editor.children in react hooks.
Indeed, there are some silent disagreements contributors were unware of. I guess it's good to ask @bryanph for clarification and his plan.
Would be nice if you did it!
@aboveyunhai slate was redesigned at some point so that the slate package is just for modifying the document and slate-react is for the react specific part (rendering the changes to the DOM). I think you got it right in the controlled component vs uncontrolled component part. I originally argued in an issue that it should be explicitly an uncontrolled component because some people were getting problems with the selection not updating as a result of setting the "controlled" value state, see https://github.com/ianstormtaylor/slate/issues/3575#issuecomment-923227427. I elaborated on this here: https://github.com/ianstormtaylor/slate/pull/4540#issuecomment-951770910.
Note that an Editor.reset method was proposed there which would then also handle the selection resetting and the history resetting (which wasn't handled by passing a different value prop). Will see if I can find some time in the next few weeks to give that a shot (or someone else can give it a shot of course :smile:). As mentioned above setting editor.children explicitly to an initial value is the recommended way for now. I agree that's not the most elegant solution but hopefully an Editor.reset API of some form will resolve that.
Changing content via Transform.transform(op) does not reflect changes visually, however the editor.children does change. Reproduction code: https://codesandbox.io/s/great-waterfall-sp2jm?file=/src/App.tsx
@bryanph What to do in this case?
@mlajtos use editor.apply instead
We are building a collaborative editing tool and this change is pretty much a killed when it comes to reconciling state from the server. Using a key to fully re-render is destructive and pretty much kills the UX as all selection state is lost. I would strongly consider not exposing an uncontrolled API as this makes it very tough to use properly for any non-basic use case, especially when external state can override application state (which is pretty much all the time in 2022).
@bryanph do you have an code snippet to explain how to do this ? the documentation for operations is laconic
This just caused me to blow about 3 million brain cells. We use Slate extensively in Payload and also need to programmatically handle incoming values. For now, I've done as described above in this thread and added a key based on initialValue to a div containing the Slate editor.
Here is the commit for anyone interested. Obviously Payload's implementation is quite complex but the actual changes here are fairly simple. This works for now but man that threw me for a loop.
For those who just need a quick workaround, here's how I fixed it in my code:
Custom useForceUpdate hook defined elsewhere:
const useForceUpdate = () => {
const [, setState] = useState<number>(0);
const forceUpdate = useCallback(() => {
setState(n => n + 1);
}, []);
return forceUpdate;
};
Code inside my component that renders <Slate>:
const forceUpdate = useForceUpdate();
useEffect(() => {
editor.children = value;
forceUpdate();
}, [editor, value, forceUpdate]);
I stumbled upon this issue when connecting the Slate value to a form (using Formik), and trying to reset it on submit.
Here's my "hack", which updates the key only when the value changes to the default. It's not a fully-controlled solution, but it did the trick:
const defaultRichTextValue: Descendant[] = [
{
type: 'paragraph',
children: [{ text: '' }],
},
]
function useResetKey(value: Descendant[]) {
const [key, setKey] = React.useState(0)
React.useEffect(() => {
if (JSON.stringify(value) === JSON.stringify(defaultRichTextValue)) {
setKey(n => n + 1)
}
}, [value])
return key
}
const RichTextEditor = ({
value,
onChange,
...props
}) => {
const resetKey = useResetKey(value)
const editor = React.useRef(withHistory(withReact(createEditor())))
return (
<Slate
editor={editor.current}
value={value}
onChange={onChange}
key={resetKey}
>
<Editable {...props} />
</Slate>
)
}
thx! @franky47 Hope this bug will be ruled out as soon as possible!
We are also seeing this as a major blocker to using Slate. We have multiple ways to display comment threads (sidebar and a floating comment overlay), and sometimes both are visible when typing a new comment, meaning they share state. It's pretty fundamental to have the ability to control an input's value externally, and in fact, one way data flow is one of the core pillars of React. This is a bug, and the key hack is just that, a hack.
this is also a dealbreaker to adoption for me.
Really loved using Slate until I faced this problem and read this thread. I have been stuck on this for a while, and the solutions mentioned here are way below the standard set by how the rest of Slate works.
The solution I used doesn't need any useEffects or extra states
<Slate editor={editor} value={value} key={JSON.stringify(value)}>
I stringify the value and use that as the key, so that when the value changes, the key changes and the component is rerendered
I've been finding various options to resolve this issue, and it appears that your solution is the simplest and most efficient method. This should be on the documentation.
Using keys and forcing the component to re-render is a no go: major performance hog and prone to errors with caret position and selection. What are our options here? Any intention to fix this issue, and if so, any particular timeline in sight? I love Slate so far, but this particular issue is making me reconsider. What legacy version can I roll back to to get controlled react components? Also, what's up with versioning here? Why is everything on 0.x.x if there were many breaking changes already? I would be willing to invest time to figure out what's going on in the source code and how we can make things controlled, but I need some indication if someone is already working on it, and if it's something that the core intends to support again - otherwise I will just fork and go.
I am maintainin https://github.com/react-page/react-page which uses slate for its rich-text editor. Since the infamous change to an uncontrolled component, we suffer from bugs and inconsistencies. Setting the value from outside is important for us, because we have our own undo/redo mechanism.
Uncontrolled components are in my opinion an antipattern in most cases and the amount of headache it created alone in this thread here shows why. I recently created another issue here https://github.com/ianstormtaylor/slate/issues/5281 but i think it a duplicate of this here.
I tried different workarounds, but I found none that works consistently. Slate really has to revert this decision, I don't think there is another way.
What we ended up doing to dynamically modify the state of slate, is passing the ref of the editor to the parent components via a Context wrapper, and using Slate API to mutate nodes. Not the most elegant solution, but it did the trick for us. Though, I would really prefer having a controlled input rather than keeping track of editor refs.