Skip to main content

Migration to Remirror v3

This guide lays out the required steps to migrate from Remirror v2 to Remirror v3.

Consult the announcement post to learn more about the v3 release, and what we're trying to achieve.

Installing

npm add --save remirror@latest @remirror/react@latest @remirror/pm@latest

Stage 3 decorators

Remirror v3 has been updated to use Stage 3 decorators, now they have reached candidate status, and esbuild supports them. Previously Remirror used TypeScript's experimental decorators, which use an older version of the spec.

If you use Remirror's decorators (@extension, @command, @helper and @keyBinding) in your custom extensions, you may find they no longer work. Here are a few things to try

TypeScript

Set experimentalDecorators to false in your tsconfig. This ensures you don't use the older version of the decorator spec.

Vite

If you are using Vite with the SWC plugin, see Next.js below, otherwise:

Ensure the esbuild version you use is at least 0.21.0, and update the target your Vite config

{
"esbuild": {
"target": "ES2020"
}
}

Babel

Update your Babel config to use the latest version of the decorator spec.

{
"plugins": [["@babel/plugin-proposal-decorators", { "version": "2023-11" }]]
}

Next.js

At time of writing Next.js does not support Stage 3 decorators.

We recommend using our legacy decorators (see below).

Help! None of the above worked

If all else fails, you can revert to using the legacy decorators. Please update your imports so that

Old nameNew name
commandlegacyCommand
helperlegacyHelper
keyBindinglegacyKeyBinding

i.e.

import { legacyCommand as command } from 'remirror';

The extension decorator is backwards compatible, so does not need updating.

Please bear in mind that these legacy decorators are considered deprecated, and will be removed in a future release of Remirror.

i18n prop removed from <Remirror />

In previous versions of Remirror, the i18n prop of the root <Remirror /> component allowed you to pass a customised Lingui instance.

With this version, we want to allow any i18n library to be used with Remirror, so the i18n prop has been removed, and replaced with an i18nFormat function.

This allows users to plug in any i18n library, by implementing a definition for this function.

This function is described by the TypeScript type I18nFormatter.

Example: Using react-i18next

import { useTranslation } from 'react-i18next';
import { Remirror, useRemirror } from '@remirror/react';

const Editor: React.FC = () => {
const { t } = useTranslation();

const i18nFormat: I18nFormatter = useCallback(
(message, values) => {
// Note only using the message ID here, more on this later
return t(message.id, values);
},
[t],
);

const { manager } = useRemirror({
extensions: () => [
// Some extensions here
],
});

return <Remirror manager={manager} i18nFormat={i18nFormat} />;
};

react-i18next, like many i18n solutions, requires you define your translatable strings up front, via key-value pairs.

To facilitate this, the @remirror/messages package now exposes the translatable strings as JSON files.

These messages are provided as key value pairs, so they can be loaded into your chosen i18n library.

Currently, only English locale (en) messages are provided.

import i18n from 'i18next';
import ICU from 'i18next-icu';
import { initReactI18next } from 'react-i18next';
import type { I18nFormatter } from 'remirror';
import allMessages from '@remirror/messages/en/all-messages.json';

// or messages for specific extension(s)
// import boldMessages from '@remirror/messages/en/extension-bold-messages.json';
// import italicMessages from '@remirror/messages/en/extension-italic-messages.json';

i18n
.use(ICU) // Required if using the provided messages from @remirror/messages
.use(initReactI18next)
.init({
resources: {
en: {
translation: allMessages,
},
},
lng: 'en',
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
});

You do not have to use the messages in these key value pairs, you could replace them with your own. They are provided for convenience, and to expose the message IDs Remirror uses.

Restoring the previous behaviour

If you wish to carry on using Remirror's default i18n solution (powered by Lingui), you will need to install the @remirror/i18n package, as this is now an optional package.

Example: Continue using @remirror/i18n

Install the i18n package, as it is now optional, and not installed by default.

npm add @remirror/i18n
import { i18nFormat } from '@remirror/i18n';
import { Remirror, useRemirror } from '@remirror/react';

const Editor: React.FC = () => {
const { manager } = useRemirror({
extensions: () => [
// Some extensions here
],
});

return <Remirror manager={manager} i18nFormat={i18nFormat} />;
};

The useI18n hook return value

As a consequence of the above, the useI18n no longer returns an object containing the Lingui i18n instance.

It now returns the i18nFormat function that was passed to the root <Remirror /> component.

Before

const { t, i18n } = useI18n();

After

const t = useI18n();

// Where "t" is the same function that was passed via `i18nFormat`

MUI based components in @remirror/react, moved to new package.

In previous versions of Remirror, menus/toolbar/buttons and "find and replace" components were exposed from @remirror/react.

As @remirror/react is a required module for Remirror, it meant @mui/material was bundled regardless of whether you used these components or not.

With this version, these components have been moved to a new optional package - @remirror/react-ui.

Other than the change in import path, there should be no changes to the MUI components themselves. The full list of affected components is included below.

Before: Remirror v2 example

import React from 'react';
import { BoldExtension } from 'remirror/extensions';
import { Remirror, ThemeProvider, ToggleBoldButton, Toolbar, useRemirror } from '@remirror/react';

const extensions = () => [new BoldExtension()];

const ToggleBold = (): JSX.Element => {
const { manager, state, onChange } = useRemirror({
extensions: extensions,
content: '<p>Text in <b>bold</b></p>',
stringHandler: 'html',
});

return (
<ThemeProvider>
<Remirror
manager={manager}
autoFocus
onChange={onChange}
initialContent={state}
autoRender='end'
>
<Toolbar>
<ToggleBoldButton />
</Toolbar>
</Remirror>
</ThemeProvider>
);
};

export default ToggleBold;

After: Diff for Remirror v3 example

import React from 'react';
import { BoldExtension } from 'remirror/extensions';
- import { Remirror, ThemeProvider, ToggleBoldButton, Toolbar, useRemirror } from '@remirror/react';
+ import { Remirror, ThemeProvider, useRemirror } from '@remirror/react';
+ import { ToggleBoldButton, Toolbar } from '@remirror/react-ui';

const extensions = () => [new BoldExtension()];

const ToggleBold = (): JSX.Element => {
const { manager, state, onChange } = useRemirror({
extensions: extensions,
content: '<p>Text in <b>bold</b></p>',
stringHandler: 'html',
});

return (
<ThemeProvider>
<Remirror
manager={manager}
autoFocus
onChange={onChange}
initialContent={state}
autoRender='end'
>
<Toolbar>
<ToggleBoldButton />
</Toolbar>
</Remirror>
</ThemeProvider>
);
};

export default ToggleBold;

Full list of affected components

These components have been moved from @remirror/react to the new optional packages @remirror/react-ui.

  • Toolbar
  • FloatingToolbar
  • MarkdownToolbar
  • WysiwygToolbar
  • VerticalDivider
  • CenterAlignButton
  • CommandButton
  • CopyButton
  • CreateTableButton
  • CutButton
  • DecreaseFontSizeButton
  • DecreaseIndentButton
  • DropdownButton
  • IncreaseFontSizeButton
  • IncreaseIndentButton
  • InsertHorizontalRuleButton
  • JustifyAlignButton
  • LeftAlignButton
  • PasteButton
  • RedoButton
  • RightAlignButton
  • ToggleBlockquoteButton
  • ToggleBoldButton
  • ToggleBulletListButton
  • ToggleCalloutButton
  • ToggleCodeBlockButton
  • ToggleCodeButton
  • ToggleColumnsButton
  • ToggleHeadingButton
  • ToggleItalicButton
  • ToggleOrderedListButton
  • ToggleStrikeButton
  • ToggleSubscriptButton
  • ToggleSuperscriptButton
  • ToggleTaskListButton
  • ToggleUnderlineButton
  • ToggleWhitespaceButton
  • UndoButton
  • BaselineButtonGroup
  • BasicFormattingButtonGroup
  • CalloutTypeButtonGroup
  • CommandButtonGroup
  • DataTransferButtonGroup
  • FormattingButtonGroup
  • HeadingLevelButtonGroup
  • HistoryButtonGroup
  • IndentationButtonGroup
  • ListButtonGroup
  • TextAlignmentButtonGroup
  • FindReplaceComponent
  • CommandMenuItem
  • ToggleCalloutMenuItem
  • ToggleHeadingMenuItem

Removed deprecated SearchExtension in favour of FindExtension

TLDR: SearchExtension has been removed from Remirror v3 completely, please use FindExtension instead.

SearchExtension has been deprecated since we released FindExtension, as FindExtension offers more features and is more performant.

Furthermore, as SearchExtension was previously exposed directly via remirror/extensions, and configurable via presetWysiwyg - we have updated both of these access points to expose FindExtension instead.

If using presetWysiwyg, the config options for SearchExtension will need updating to their FindExtension equivalents.

Before: Remirror v2 example

import React from 'react';
import { wysiwygPreset } from 'remirror/extensions';
import { Remirror, ThemeProvider, ToggleBoldButton, Toolbar, useRemirror } from '@remirror/react';

const extensions = () =>
wysiwygPreset({
alwaysSearch: true,
});

const UsingWysiwygPreset = (): JSX.Element => {
const { manager, state, onChange } = useRemirror({
extensions: extensions,
content: '<p>Text to search</p>',
stringHandler: 'html',
});

return (
<ThemeProvider>
<Remirror
manager={manager}
autoFocus
onChange={onChange}
initialContent={state}
autoRender='end'
/>
</ThemeProvider>
);
};

export default UsingWysiwygPreset;

After: Diff for Remirror v3 example

import React from 'react';
import { wysiwygPreset } from "remirror/extensions";
import { Remirror, ThemeProvider, ToggleBoldButton, Toolbar, useRemirror } from '@remirror/react';

const extensions = () => wysiwygPreset({
- alwaysSearch: true,
+ alwaysFind: true,
});

// Rest as above

Removed deprecated command dry run function isEnabled, use enabled instead.

When using commands or chained commands, you can "dry run" the command to see if it can be executed against the current editor state.

For instance, you can check if toggleBold is enabled by running toggleBold.enabled() which returns a boolean indicating whether it is possible, without actually changing the editor's state.

.isEnabled() was an alias of .enabled(), but this alias has now been removed. Please use .enabled() instead.

const { toggleBold } = useCommands();

const handleClick = useCallback(() => {
if (toggleBold.isEnabled()) {
toggleBold();
}
}, [toggleBold]);
const { toggleBold } = useCommands();

const handleClick = useCallback(() => {
- if (toggleBold.isEnabled()) {
+ if (toggleBold.enabled()) {
toggleBold();
}
}, [toggleBold]);

CodeBlockLanguageSelector component moved to @remirror/react-ui package

The CodeBlockLanguageSelector component has been moved from @remirror/extension-react-language-select to @remirror/react-ui.

While it was originally named as an "extension", upon closer examination, we've realised that its role aligns more with that of a component, rather than a true extension.

To maintain the integrity of our definition of an extension, we believe this move is necessary. This is to help provide a more accurate representation of functionality, and enhance overall understanding and usage of the library.

Additionally CodeBlockLanguageSelector now renders a MUI Select component, rather than a native select element. This enables us to utilise the "auto width" behaviour, rather than implementing this behaviour ourselves.

Furthermore, it will now render in the top right corner of the code block by default, rather than the top left. Passing position='left' will revert to rendering in the top left corner as before.

Before: Remirror v2 example

import 'remirror/styles/all.css';

import React from 'react';
import css from 'refractor/lang/css.js';
import javascript from 'refractor/lang/javascript.js';
import typescript from 'refractor/lang/typescript.js';
import { CodeBlockExtension } from 'remirror/extensions';
import { CodeBlockLanguageSelect } from '@remirror/extension-react-language-select';
import { Remirror, ThemeProvider, useRemirror } from '@remirror/react';

const extensions = () => [
new CodeBlockExtension({
supportedLanguages: [css, javascript, json, markdown, typescript],
}),
];

const content = `
<pre><code data-code-block-language="typescript">function sayHello {
console.log('Hello world, TypeScript!')
}</code></pre>
`;

const EditorWithCodeBlocks = (): JSX.Element => {
const { manager, state } = useRemirror({ extensions, content, stringHandler: 'html' });

return (
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender>
<CodeBlockLanguageSelect />
</Remirror>
</ThemeProvider>
);
};

export default EditorWithCodeBlocks;

After: Diff for Remirror v3 example

import 'remirror/styles/all.css';

import React from 'react';
import css from 'refractor/lang/css.js';
import javascript from 'refractor/lang/javascript.js';
import typescript from 'refractor/lang/typescript.js';
import { CodeBlockExtension } from 'remirror/extensions';
- import { CodeBlockLanguageSelect } from '@remirror/extension-react-language-select';
import { Remirror, ThemeProvider, useRemirror } from '@remirror/react';
+ import { CodeBlockLanguageSelect } from '@remirror/react-ui';

const extensions = () => [
new CodeBlockExtension({
supportedLanguages: [css, javascript, json, markdown, typescript],
}),
];

const content = `
<pre><code data-code-block-language="typescript">function sayHello {
console.log('Hello world, TypeScript!')
}</code></pre>
`;

const EditorWithCodeBlocks = (): JSX.Element => {
const { manager, state } = useRemirror({ extensions, content, stringHandler: 'html' });

return (
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender>
<CodeBlockLanguageSelect />
</Remirror>
</ThemeProvider>
);
};

export default EditorWithCodeBlocks;

Removed CodeMirror and Yjs extensions from default remirror bundle

The CodeMirror (v5) and Yjs extensions are no longer bundled with Remirror by default. If you require these extensions, Remirror v3 requires you to install these extensions directly.

Before: Remirror v2 example

import React from 'react';
import { BlockquoteExtension, CodeMirrorExtension, YjsExtension } from 'remirror/extensions';
import { Remirror, ThemeProvider, ToggleBoldButton, Toolbar, useRemirror } from '@remirror/react';

const extensions = () => [
// Extension options omitted for brevity
new BlockquoteExtension(),
new CodeMirrorExtension(),
new YjsExtension(),
];

/*
Then load your extensions into Remirror

const { manager, state, onChange } = useRemirror({
extensions: extensions,
content: '<p>Hello Remirror!</p>',
stringHandler: 'html',
});
*/

After Remirror v3 example

import React from 'react';
- import { BlockquoteExtension, CodeMirrorExtension, YjsExtension } from 'remirror/extensions';
+ import { BlockquoteExtension } from 'remirror/extensions';
import { Remirror, ThemeProvider, ToggleBoldButton, Toolbar, useRemirror } from '@remirror/react';
+ import { CodeMirrorExtension } from '@remirror/extension-codemirror5';
+ import { YjsExtension } from '@remirror/extension-yjs';

const extensions = () => [
// Extension options omitted for brevity (unchanged from v2)
new BlockquoteExtension(),
new CodeMirrorExtension(),
new YjsExtension(),
];

/*
Then load your extensions into Remirror

const { manager, state, onChange } = useRemirror({
extensions: extensions,
content: '<p>Hello Remirror!</p>',
stringHandler: 'html',
});
*/

Removed React Tables extension from default @remirror/react bundle

The React Tables extension is no longer bundled with Remirror by default. If you require this extension, Remirror v3 requires you to install the extension directly.

Before: Remirror v2 example

import React from 'react';
import { BoldExtension } from 'remirror/extensions';
import {
Remirror,
TableComponents,
TableExtension,
ThemeProvider,
useRemirror,
} from '@remirror/react';

const extensions = () => [
// Extension options omitted for brevity
new BoldExtension(),
new TableExtension(),
];

const content = '<p>Hello Remirror!</p>';

const EditorWithReactTables = (): JSX.Element => {
const { manager, state } = useRemirror({ extensions, content, stringHandler: 'html' });

return (
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender>
<TableComponents />
</Remirror>
</ThemeProvider>
);
};

export default EditorWithReactTables;

After Remirror v3 example

import React from 'react';
import { BoldExtension } from 'remirror/extensions';
+ import { TableComponents, TableExtension } from '@remirror/extension-react-tables';
import {
Remirror,
- TableComponents,
- TableExtension,
ThemeProvider,
useRemirror,
} from '@remirror/react';

const extensions = () => [
// Extension options omitted for brevity (unchanged from v2)
new BoldExtension(),
new TableExtension(),
];

const content = '<p>Hello Remirror!</p>';

const EditorWithReactTables = (): JSX.Element => {
const { manager, state } = useRemirror({ extensions, content, stringHandler: 'html' });

return (
<ThemeProvider>
<Remirror manager={manager} initialContent={state} autoRender>
<TableComponents />
</Remirror>
</ThemeProvider>
);
};

export default EditorWithReactTables;

Update any usages of extensionDecorator to extension

The extensionDecorator decorator was deprecated alias and has now been removed. Please use extension instead

Update any usages of useSuggester to useSuggest

The useSuggester React hook was deprecated alias and has now been removed. Please use the useSuggest hook instead.

Similarly, the type of this function has been renamed, please update any usages of UseSuggesterProps to UseSuggestProps.

Update any usages of getRemirrorJSON to useSuggest

The getRemirrorJSON helper function was deprecated alias and has now been removed. Please use the getJSON helper instead.

jest-prosemirror's jumpTo has been removed

Please use selectText instead.

jest-remirror updates

We have removed the deprecated properties start and end, use from and to respectively instead.

Additionally, we have removed the deprecated function jsdomExtras, please use jsdomPolyfills instead.

@remirror/theme updates

We have removed the deprecated functions getTheme and getThemeProps, please use getThemeVar and getThemeVarName respectively.

Removed type definitions

We have removed the following types, please use the alternative suggested below.

Old nameNew name
ClickHandlerClickEventHandler
ClickMarkHandlerClickMarkEventHandler
PromiseValueAwaited (built in)
MutableWritable
UseSuggesterPropsUseSuggestProps

Feedback

If you spot anything we have missed in this guide, or if you run into any issues, please reach out to us on Discord.