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!
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: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!