Make it easy to programatically focus a remirror editor, including derivatives
See original GitHub issueDescription
In our existing (DraftJS) codebase, we use React refs to store a link to the editor, and we do ref.current.focus()
to focus the editor and ref.current.blur()
to unfocus it. We’re trying to port over to Remirror; but we cannot find a way to focus an existing Remirror editor cleanly when using derivative Remirror editors, such as SocialEditor
.
Workaround: pass a child to the editor and use this to get access to the context:
import React, { FC } from "react";
import {
useRemirrorContext,
InjectedRemirrorProps,
} from "@remirror/react";
type RemirrorContext = InjectedRemirrorProps<any>;
/**
* Using this to make the beautiful React hooks API usable from an ugly class.
*/
const GetRemirrorContext: FC<{
store: (ctx: RemirrorContext) => void;
}> = ({ store }) => {
const context = useRemirrorContext();
useEffect(() => {
store(context);
}, [context, store]);
return null;
};
/**
* React element with `focus` and `blur` methods.
*/
class FocusableChild extends React.Component {
remirrorContext: RemirrorContext | null = null;
setRemirrorContext = (ctx: RemirrorContext): void => {
this.remirrorContext = ctx;
};
getView(): EditorView<EditorSchema<any, any>> | null {
return this.remirrorContext?.view ?? null;
}
focus(): void {
const view = this.getView();
if (view) {
view.focus();
}
}
blur(): void {
// Remirror doesn't have a blur method, so we'll just swallow this for now.
}
render(): JSX.Element {
return <GetRemirrorContext store={this.setRemirrorContext} />;
}
}
const MyRemirrorEditor: FC<any> = props => {
// ...
return (
<SocialEditor
userData={userMatches}
tagData={tagMatches}
onMentionChange={onMentionChange}
extensions={extensions}
>
{/* 👇👇👇👇👇👇 */}
<FocusableChild ref={props.editorRef} />
{/* 👆👆👆👆👆👆 */}
</SocialEditor>
);
}
Possible Implementation
Separate the Remirror context from derivative editors, and make everyone always explicitly render a RemirrorProvider
so you always have to render <RemirrorProvider><RemirrorDerivativeEditorOrMyOwnStuff ... /></RemirrorProvider>
. This way we can get access to useRemirrorContext
-> context.view whenever we need it.
In an ideal world, we’d be able to just call context.focus()
from our own code independent of which Remirror editor or derivative we’re using under the hood:
import React from "react";
import Mousetrap from "mousetrap";
import { useRemirrorContext, RemirrorProvider } from "@remirror/react";
import { SocialEditor } from "@remirror/editor-social";
const MyRemirrorEditor = () => {
const context = useRemirrorContext();
useEffect(() => {
Mousetrap.bind("c", event => {
event.preventDefault();
context.focus(); // Or context.view?.focus()
});
return () => {
Mousetrap.unbind("c");
};
}, [context]);
// ...
return <SocialEditor ... />
// or <SomeOtherEditor /> or <CustomEditorWithLoadsOfExtensions />
}
ReactDOM.render(
<RemirrorProvider>
<MyRemirrorEditor />
</RemirrorProvider>,
document.getElementById('example')
);
Checklist
- I have read the contributing document.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:2
- Comments:6 (5 by maintainers)
Following some discussion, we’re looking at an API that might look something like this.
I’m going to try and lead through the example, so the code will be a little out of order…
First, we want to render our application:
Our application consists solely of a Remirror editor.
Importantly, we need to be able to get a Prosemirror schema from our extensions in order to load data, so we do this by building a RemirrorManager using a list of extensions:
Lets imagine our content is stored in LocalStorage, we might get it like this:
Now that we have our manager (which contains our constructed Prosemirror schema) and our content, we can initialise our editor state and store it using
useState
:When the content changes, we might want to save it back to localStorage (NOTE: you should debounce this!):
Finally we can render our controlled RemirrorProvider, which will give all our lower down code access to the manager and the state. Note that if we do not want Remirror to be controlled we can simply pass
manager
and omit the other props.Now to implement
MyRemirrorEditor
, which is where most of the runtime-modifications and configuration lives.First thing is that we need access to our Remirror context, which contains access to the Prosemirror schema, and also our current editor state:
Since we’re using the social editor, we need a way to figure out who has been mentioned.
Every extension comes with a handy set of react hooks we can use to register our handlers for such things, and these handlers can be replaced on each render without issue (though using
useCallback
would definitely be more efficient!).Here’s a handler to fetch some users that could have been mentioned:
If we need it, we could get the Prosemirror schema, plugins, editor state, etc:
We might want to add a global hotkey with mousetrap to focus the editor:
Finally we render our actual editor - it doesn’t need any props because it can get everything from
useRemirror()
.I’ve created a
next
branch (#251) were the API changes and progress can be followed.Additionally, there’s an open PR (#252) which adds a
focus()
method to the remirror context.