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

Add support for subscribing to changes on subsets of the EditorState #147

Open
smoores-dev opened this issue Dec 17, 2024 · 2 comments
Open

Comments

@smoores-dev
Copy link
Collaborator

smoores-dev commented Dec 17, 2024

It's pretty common to have a React component that needs access to some part of the EditorState in the render function (e.g. a formatting button that checks the current selection's marks to determine whether it should be activated). Sometimes, React node view components also need to access parts of the EditorState outside of their own node.

At the moment, the way to do this is with useEditorState, which triggers a re-render for the consuming component on every single update to the document.

It's possible that we can't do much better than this, but now that we have very good render memoization in the beta release, it would be nice to provide a way for components to access parts of the EditorState (say, a plugin state) without needing to rerender on every update.

One way to accomplish this could be to have a new hook, useSelectEditorState, that takes a selector function as an argument. I'm working through this in my head as I write it, so forgive me if this doesn't pan out, but I'm imagining something like:

function useSelectEditorState<Value>(
  selector: (state: EditorState) => Value
) {
  const view = useContext(EditorViewContext)
  // We access the view directly to
  // avoid a first render with an
  // undefined value
  const [selected, setSelected] = useState(() => selector(view.state))
  // can’t actually use `useEditorEffect` here,
  // need a way to register something
  // that runs on every state update
  useEditorEffect((view) => {
    setSelected(view.state)
  }, [selector])

  return selected
}

I think this literal code doesn’t work. As noted in the comment, we need a new effect registration hook (internal) to register an effect that runs on every state update. Also, I think as written this could introduce state tearing, because the selected value is updated in a layout effect, so during the render after a state update, the actual editorstate will be “ahead” of the selected value.

Again, it’s possible that there isn’t a clean way to do this (some part of me keeps saying “use useSyncExternalStore!" But I have no idea if that’s right) without state tearing, in which case we shouldn’t do it. But I keep thinking about it so I figured I should share!

@tilgovi
Copy link
Contributor

tilgovi commented Dec 17, 2024

I think even with useSyncExternalStore there are some state issues that come up. React Redux tries to detect these issues, I think, and re-render automatically. They discuss it in their warnings about their hooks API.

I also wonder if we're drawn to a Redux-like API for familiarity reasons, when other folks might not immediately reach for something like useSelector. We could wait and see what solutions folks come up with if they run into performance issues.

@smoores-dev
Copy link
Collaborator Author

Yeah I think there's no urgency on this, I just wanted to get it down/out of my head. I haven't run into an actual performance issue caused by this yet, but I keep thinking about it because I had to be very deliberate in order to avoid it in the StrictMode work! I agree that it makes sense to accumulate some cases where this is actually an issue and get feedback on what folks would want here

@smoores-dev smoores-dev changed the title Add a selector to useEditorState Add support for subscribing to changes on subsets of the EditorState Dec 18, 2024
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

No branches or pull requests

2 participants