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

Support strict mode and add some memoization #135

Closed
wants to merge 3 commits into from

Conversation

smoores-dev
Copy link
Collaborator

Ok so! In order to resolve #128, and more generally support React's Strict Mode and concurrent rendering, we need to make it possible to re-render our components in isolation. Right now, in order to build the ViewDescriptor tree, we have to render the entire tree. This means that concurrent behaviors like re-using state and interrupting cause issues (the issue in #128 is that if a text node gets re-rendered in isolation, its view descriptor no longer has a correct reference to its parent).

This PR takes an approach to resolve this. We're having the reactKeys plugin do double duty; while it builds and tracks the map from positions to keys, it also tracks parent child relationships between keys.

Each node now registers its view descriptor with a single shared context, using its key. It can find its child and parent view descriptors by their keys, using the new maps from the react keys plugin.

A neat side benefit of this is that, since it's now safe to render only parts of the tree at a time, we can memoize our components. This can lead to a pretty significant reduction in React rendering work; if typing at the end of the last paragraph in a document, none of the previous block nodes will re-render as you type.

@smoores-dev
Copy link
Collaborator Author

@tilgovi I'm still working on this and it's breaking some tests at the moment,, but I'd still love your thoughts on it if you have any

@@ -16,6 +16,7 @@
"node": false
},
"rules": {
"react/prop-types": "off",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have to check this in, I'm just turning it off for new because using React.memo inline breaks this rule for some reason.

Comment on lines +25 to +31
const { decorations, viewDescContext, node, state, pos } = this.props;
const reactKeysState = state && reactKeysPluginKey.getState(state);
const key = reactKeysState?.posToKey.get(pos);
const parentKey = key && reactKeysState?.keyToParent.get(key);
const parent =
parentKey !== undefined ? viewDescContext[parentKey] : undefined;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix to #128: when Strict Mode re-renders just the TextNodeView, we are still able to use the key map to find the parent view descriptor, so we can maintain the bidirectionality of the view descriptor tree

import { Plugin, PluginKey } from "prosemirror-state";

export function createNodeKey() {
const key = Math.floor(Math.random() * 0xffffff).toString(16);
return key;
}

const rootKey = "ROOT_KEY";
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a stand-in; probably should be a Symbol or something.

@@ -79,7 +80,10 @@ type SharedMarksProps = {
childViews: Child[];
};

function InlineView({ innerPos, childViews }: SharedMarksProps) {
const InlineView = memo(function InlineView({
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the changes here are just wrapping components with memo

@smoores-dev
Copy link
Collaborator Author

Ah, I forgot about MarkViews and WidgetViews haha

@smoores-dev
Copy link
Collaborator Author

Yeah I don't think there's an easy way to make this work for mark and widget views. We get to sort of cheat with the actual React element keys, because mark views always wrap their child node views (and so can have the same keys, since keys only need to be unique at a given level of the tree) and widgets get given keys manually at construction.

But for this usage, we would need to know these keys during the state update (and without access to the decorations, necessarily), and they need to be a distinct set (meaning that mark keys need to be distinct from their child node keys). I don't think that's feasible.

I'm still not 100% sure that it really makes sense for us to worry about strict mode/concurrent rendering, aside from the annoying (but minor) bug in strict mode, #128. It may be enough to just say "You can't have a Suspense boundary within a ProseMirror doc". The memoization would have been nice to work out, and there may be a way to take some of this work and put it into changes that are only concerned with memoization.

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 this pull request may close these issues.

1 participant