Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error: Cannot find a descendant at path [0] in node - CRA live-reload #4081

Open
marcoSven opened this issue Feb 17, 2021 · 20 comments · May be fixed by #5407
Open

Error: Cannot find a descendant at path [0] in node - CRA live-reload #4081

marcoSven opened this issue Feb 17, 2021 · 20 comments · May be fixed by #5407

Comments

@marcoSven
Copy link

Do you want to request a feature or report a bug?

Bug

What's the current behavior?

Create React App live editing error on every change. Using installation instructions

To reproduce the error see this repository with a "workaround" to prevent the error.

Sandbox URL
Edit the file and you should see the error. Uncomment the lines suggested above to prevent the error.

Slate: 0.59.0
Browser: Chrome
OS: Mac

What's the expected behavior?

Live-editing should work without errors.

@lukesmurray
Copy link
Contributor

This error occurs because useMemo is reevaluated on reloads.

To get around the issue you can use useState.

const [editor] = useState(createEditor())

useState is not reevaluated on reloads.

I'm not sure if there is any downside to replacing useMemo with useState but if you want to be even more cautious you could probably use [use-constant](https://github.com/Andarist/use-constant) or something similar to get the behavior you want.

@Mangatt
Copy link
Contributor

Mangatt commented Feb 18, 2021

I can confirm that in 0.60.4 with react-refresh.

Why is this happening? Slate should be able to receive new editor instance without crashing, no?

@lukesmurray
Copy link
Contributor

It's listed in the docs that the slate editor must be stable across renders. I believe these issues occur because information about the dom is stored in weak maps on the editor singleton. For example slate doesn't manage selection at all so information about the current selected node is stored in the editor singleton. When the editor is recreated that information is lost which leads to errors. The workaround here is one line and as far as I can tell it has no obvious downsides.

@Mangatt
Copy link
Contributor

Mangatt commented Feb 18, 2021

Thanks @lukesmurray for clarification.

It might be good to use ref internally in Slate component for editor instance then. Simple warning instead of crash would be enough I guess.

Question for mantainers: Is there any reason why this shouldn't be done? Otherwise I'll create PR.

@lukesmurray
Copy link
Contributor

#3233 may be relevant.

@chasefarmer2808
Copy link

chasefarmer2808 commented Feb 18, 2021

Still having the same even when doing [editor] = useState() My situation is a bit different in that I need to initialize my editor after a promise resolves.

@chasefarmer2808
Copy link

@macroSven could you explain why your workaround in your codesandbox prevents the error from happening?

@Mangatt
Copy link
Contributor

Mangatt commented Feb 19, 2021

This is code that we use:

const editorRef = useRef()
if (!editorRef.current) editorRef.current = withReact(createEditor())
const editor = editorRef.current

@marko-hologram
Copy link

If you are using a version of CRA that uses Fast Refresh for hot reload and don't want to switch from official Slate examples that use useMemo, you can just place this comment at the top of your editor component file // @refresh reset

This should force that particular file/component to be "remounted" and avoids this error, but it doesn't use Fast Refresh as expected then. It reloads the component, doesn't "refresh" it in place.


Still having the same even when doing [editor] = useState() My situation is a bit different in that I need to initialize my editor after a promise resolves.

Could you just conditionally load the editor you want to initialize? Store some initial state in useState and then after Promise resolves, update that state and conditionally load the editor with everything you need? I'm not sure if this fits your case and if it would work properly, it's just an idea.

someState && <MyRichTextEditor />

@chasefarmer2808
Copy link

Thanks @marko-hologram. The conditional editor load is what I ended up doing. And I never new about the refresh comment. Will give that a try too!

@aliak00
Copy link

aliak00 commented Mar 13, 2021

So these two workaround work:

const [editor] = useState(withReact(createEditor()));

and

 const editorRef = useRef<Editor>();
 if (!editorRef.current) editorRef.current = withReact(createEditor());
 const editor = editorRef.current;

What's the difference between the two and are there any pros/cons one should know about?

@mwood23
Copy link
Contributor

mwood23 commented Mar 14, 2021

The workaround I use is FAST_REFRESH=false react-scripts start. Most of my project is an editor and I've seen it be weird with WeakMaps, memos, and other things This turns it off for your entire app.

@Torvin
Copy link

Torvin commented Sep 29, 2021

const [editor] = useState(withReact(createEditor()));

@aliak00 this one creates (and throws away) a new instance of the editor on every re-render, which I guess has slight performance and GC implications. The second solution with useRef doesn't have these disadvantages.

@marko-hologram
Copy link

const [editor] = useState(withReact(createEditor()));

@aliak00 this one creates (and throws away) a new instance of the editor on every re-render, which I guess has slight performance and GC implications. The second solution with useRef doesn't have these disadvantages.

I think this example can be updated to use "lazy initial state" (https://reactjs.org/docs/hooks-reference.html#lazy-initial-state) that should only be computed on initial render.

const [editor] = useState(() => withReact(createEditor()));

In subsequent renders this won't be called and evaluated so unnecessary instances of editor won't be created.

@aperepelytsya
Copy link

This is code that we use:

const editorRef = useRef()
if (!editorRef.current) editorRef.current = withReact(createEditor())
const editor = editorRef.current

Why we can't just:

const editorRef = useRef(withReact(createEditor()))
const editor = editorRef.current

@marko-hologram
Copy link

This is code that we use:

const editorRef = useRef()
if (!editorRef.current) editorRef.current = withReact(createEditor())
const editor = editorRef.current

Why we can't just:

const editorRef = useRef(withReact(createEditor()))
const editor = editorRef.current

I think this example will just call withReact(createEditor()) on each re-render just because it's a normal function call that appears when component code gets executed again.

You can probably test this by creating a custom plugin that just does a console.log. Then init your editor with that custom plugin and see if new editor instance is created on each re-render by checking if that console.log is called each time.

@eden-lane
Copy link
Contributor

What if I need to recreate editor's instance on some event?

@marko-hologram
Copy link

What if I need to recreate editor's instance on some event?

Not sure what would be the use case for it, but I'm guessing it could be done like this:

const [editor, setEditor] = useState(() => withReact(createEditor()));

...

const handleClick = () => {
  setEditor(withReact(createEditor()));
}

If editor instance is kept in state, then just replace that state potentially. I haven't really worked with Slate in a long time so I forgot how most things here work 😄

@Hamzaalam
Copy link

Hamzaalam commented Aug 11, 2022

useState

Hey,
anyone have idea how can we implement this with plugins? I have tried this approach but end up with undefined error

  const withPlugins = [withReact, withHistory, withLinks, withBlockID] as const;
  const editorRef: any = useRef()
  if (!editorRef.current) editorRef.current = useState(() => pipe(createEditor(), ...withPlugins));
  const editor: any = editorRef.current
  if (!editor) { return; }
react-editor.ts:79 Uncaught Error: Unable to find the path for Slate node: [{"children":[],"operations":[],"selection":null,"marks":null,"history":{"undos":[],"redos":[]},"removedIDs":{}},null]

@trueadm
Copy link

trueadm commented Dec 17, 2022

Just thought I'd let people know, if you're using your own ref, be sure to null it out in an effect otherwise this can lead to memory leaks – especially in React 18. We ran into similar issues in Lexical trying to use refs this way and ended up making useMemo work properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.