question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Make it easy to programatically focus a remirror editor, including derivatives

See original GitHub issue

Description

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

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:2
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
benjiecommented, Feb 27, 2020

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:

import React from 'react';
import { render } from 'react-dom';

render(<App />, document.getElementById('example'));

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:

import { useRemirrorManager, RemirrorProvider } from '@remirror/react';
import remirrorSocial from '@remirror/social';
import remirrorWysiwyg from '@remirror/wysiwyg';

const App = () => {
  const manager = useRemirrorManager([
    remirrorSocial({
      characterLimit: 280,
      shiftEnterHardBreak: false,
    }),
    remirrorWysiwyg({}),
  ]);

Lets imagine our content is stored in LocalStorage, we might get it like this:

  const content = useMemo(() => {
    try {
      return JSON.parse(localStorage.getItem('remirrorState'));
    } catch {
      return null;
    }
  }, []);

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:

  const [editorState, setEditorState] = useState(
    content
      ? manager.createStateFromContent(content)
      : manager.createEmptyState()
  );

When the content changes, we might want to save it back to localStorage (NOTE: you should debounce this!):

  useEffect(() => {
    localStorage.setItem('remirrorState', JSON.stringify(
      manager.stateToJSON(editorState)
    ));
  }, [editorState, context]);

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.

  return (
    <RemirrorProvider
      manager={manager}
      value={editorState}
      onChange={(newState) => setEditorState(newState)}
    >
      <MyRemirrorEditor />
    </RemirrorProvider>
  );
}

Now to implement MyRemirrorEditor, which is where most of the runtime-modifications and configuration lives.

import { useRemirror, Remirror } from "@remirror/react";
import { useRemirrorSocialMentionCallback } from "@remirror/social";

const MyRemirrorEditor = () => {

First thing is that we need access to our Remirror context, which contains access to the Prosemirror schema, and also our current editor state:

  const remirror = useRemirror();

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:

  useRemirrorSocialMentionCallback(async text => {
    const result = await fetch(
      `http://example.com/api?search=${encodeURIComponent(text)}`
    );
    return result.json();
  })

If we need it, we could get the Prosemirror schema, plugins, editor state, etc:

  // const schema = remirror.getSchema();
  // const plugins = remirror.getPlugins();
  // const editorState = remirror.getEditorState();

We might want to add a global hotkey with mousetrap to focus the editor:

  useEffect(() => {
    Mousetrap.bind("c", event => {
      event.preventDefault();
      remirror.focus();
    });

    return () => {
      Mousetrap.unbind("c");
    };
  }, [context]);

Finally we render our actual editor - it doesn’t need any props because it can get everything from useRemirror().

  return (
    <Remirror />
  );
}
1reaction
ifiokjrcommented, Mar 1, 2020

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Make it easy to programatically focus a remirror editor, including ...
We're trying to port over to Remirror; but we cannot find a way to focus an existing Remirror editor cleanly when using derivative...
Read more >
focus | Remirror
Set the focus for the editor. If using this with chaining this should only be placed at the end of the chain. It...
Read more >
Remirror: ProseMirror toolkit for React - Morioh
Remirror : ProseMirror toolkit for React. A toolkit for building cross-platform text editors. in the framework of your choice.
Read more >
Shakespearean Secularizations - eScholarship@McGill
was given by the department to take a course on Shakespeare taught by the ... has not been easy to assess with precision,...
Read more >
misnomer - OSCHINA - 中文开源技术交流社区
Git 2.4.5 发布,此版本更新内容如下: * The setup code used to die when core.bare and core.worktree are set inconsistently, even for commands that do not ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found