Skip to main content

Commands and helpers

Now we have a working editor and the beginning of a menu. Time to wire up the menu, so users can change the document. And there is another important piece missing: We need to observe changes to save them to a backend.

Remirror enables this via commands and helpers. You can think of commands as write actions, and helpers as read actions.

Commands

The only way to modify a Remirror document is via commands - even when you are typing in a Remirror editor, you are creating insertText commands behind the scenes.

Commands are provided by extensions, and they may be triggered by input rules (i.e. **bold**), or keyboard shortcuts (i.e. Ctrl/Cmd + B).

They can also be triggered manually, this is useful when creating custom components that trigger behaviour. @remirror/react exposes the hooks useCommands or useChainedCommands for exactly this purpose.

Bold content

Let's toggle the bold status when the user clicks the bold button:

import { useCommands } from '@remirror/react';

export const Menu = () => {
const { toggleBold, focus } = useCommands();

return (
<button
onClick={() => {
toggleBold();
focus();
}}
>
B
</button>
);
};

Visualize if content is bold

Users can now toggle the bold state but the button doesn't give any indication if the current text is bold or not. This can be added via another hook: useActive:

import { useActive, useCommands } from '@remirror/react';

export const Menu = () => {
const { toggleBold, focus } = useCommands();
const active = useActive();

return (
<button
onClick={() => {
toggleBold();
focus();
}}
style={{ fontWeight: active.bold() ? 'bold' : undefined }}
>
B
</button>
);
};

Another way of bolding

One cumbersome part of above click handler is that we have to execute the toggleBold and focus command after each other. Remirror offers chaining for a more natural way of executing multiple commands:

import React from 'react';
import { useActive, useChainedCommands } from '@remirror/react';

export const Menu = () => {
// Using command chaining
const chain = useChainedCommands();
const active = useActive();

return (
<button
onClick={() => {
chain // Begin a chain
.toggleBold()
.focus()
.run(); // A chain must always be terminated with `.run()`
}}
style={{ fontWeight: active.bold() ? 'bold' : undefined }}
>
B
</button>
);
};

Disable menu button if it can't be used

However, be aware that a command might not make sense in the current selection context. For instance, you cannot bold text within a code block.

It is good UX practice to disable (or hide) actions that are not available at the moment. With Remirror you can check if a command is enabled using its .enabled() property:

import React from 'react';
import { useCommands } from '@remirror/react';

export const Menu = () => {
// Access the commands and the activity status of the editor.
const { toggleBold } = useCommands();

return (
<button onClick={() => toggleBold()} disabled={toggleBold.enabled() === false}>
B
</button>
);
};

toggleBold is a very simple command with no arguments, others are more complex and expect arguments, like toggleHeading

toggleHeading({ level: 1 });

In these cases it is good practice to pass the same arguments to .enabled, to ensure accurate results.

toggleHeading.enabled({ level: 1 });
note

How this works (advanced)

Commands and their .enabled property execute the exact same code - the difference is whether they are dispatched to the document or not.

Commands must do two things:

  1. Return true or false, to indicate whether they are enabled right now.
  2. Check whether the dispatch property is present.
    • If dispatch is present, the command will modify the document.
    • If dispatch is not present, the command will not modify the document (this is the .enabled() case, checking if we could execute the command).

Example here

Helpers

Helpers allow you to read state from the Remirror document. They are provided by extensions - just as commands are. You can access the helpers via the useHelpers hook.

Let's save the Remirror document when the user presses Ctrl/Cmd + S! For that, we'll use a helper to extract the JSON representation of the document state:

import React, { useCallback } from 'react';
import { BoldExtension } from 'remirror/extensions';
import { Remirror, useHelpers, useKeymap, useRemirror } from '@remirror/react';

// Hooks can be added to the context without the need for creating custom components
const hooks = [
() => {
const { getJSON } = useHelpers();

const handleSaveShortcut = useCallback(
({ state }) => {
console.log(`Save to backend: ${JSON.stringify(getJSON(state))}`);

return true; // Prevents any further key handlers from being run.
},
[getJSON],
);

// "Mod" means platform agnostic modifier key - i.e. Ctrl on Windows, or Cmd on MacOS
useKeymap('Mod-s', handleSaveShortcut);
},
];

const Editor = () => {
const { manager, state } = useRemirror({ extensions: () => [new BoldExtension()] });

// Using the hooks prop to inject functionality that doesn't require rendered elements
return <Remirror manager={manager} initialContent={state} hooks={hooks} />;
};

Here are a few useful helpers, but there are many more!

HelperExtensionDescription
getJSON(Built in)JSON representation (not stringified)
getText(Built in)Plain text representation (maintains line breaks)
getHTML(Built in)HTML representation (using schema toDOM)
getMarkdownmarkdown-extensionMarkdown representation (where possible)

With that, we have a UI to interact with our editor. Next, we'll extend the functionalities of the editor itself.