slate: 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
About this issue
- Original URL
- State: open
- Created 3 years ago
- Reactions: 49
- Comments: 38 (5 by maintainers)
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
todefaultValue
orinitialValue
) as thevalue
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).
The solution I used doesn’t need any useEffects or extra states
I stringify the value and use that as the key, so that when the value changes, the key changes and the component is rerendered
Ouch, we got bitten hard by this. Downgraded for now.
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.
@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.
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.
For those who just need a quick workaround, here’s how I fixed it in my code:
Custom useForceUpdate hook defined elsewhere:
Code inside my component that renders
<Slate>
: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 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.
(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.)
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).
this is also a dealbreaker to adoption for me.
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:
@aboveyunhai slate was redesigned at some point so that the
slate
package is just for modifying the document andslate-react
is for the react specific part (rendering the changes to the DOM). I think you got it right in thecontrolled component
vsuncontrolled 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 differentvalue
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 😄). As mentioned above settingeditor.children
explicitly to an initial value is the recommended way for now. I agree that’s not the most elegant solution but hopefully anEditor.reset
API of some form will resolve that.the fix is in documentation, I found it after few hours …
If you want to update the editor's content in response to events from outside of Slate, you need to change the children property directly. The simplest way is to replace the value of editor.children editor.children = newValue and trigger a re-rendering (e.g. by calling editor.onChange() in the example above). Alternatively, you can use Slate's internal operations to transform the value, for example:
https://docs.slatejs.org/walkthroughs/06-saving-to-a-databaseSame problem here, we tried to update from an old version of Slate so we could benefit from the use of plain objects instead on Inmutable but this is preventing us from completing the migration. We might need to look for alternatives if Slate doesn’t provide a non-hacky way to control the component value. The solutions above break the editor selection when externally changing the value.
@mlajtos use
editor.apply
insteadCan 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
orvisually 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.
Just to add in, I have to make my editor controllable as well, so I did something like this, using the
key
idea as described above, but only update thekey
when the change comes from external sources.and I just use
setValue
anywhere I need to update the state outside Slate.Can’t say rerendering the entire editor is good for performance, but this is the only solution applicable to our app; assigning to
editor.children
looks too un-Reacty and sometimes causes errors.I just wanted to chime in, I really wanted to use slate for my project but it became unusable to me because of the lack of a simple way to update the state externally. If I had the time I’d try to come up with a pull request and change it myself, but I don’t have the knowledge to do it in a reasonable time frame.
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.
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 oninitialValue
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.