Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/rip21/react-simplemde-editor

React wrapper for simplemde (easymde) markdown editor
https://github.com/rip21/react-simplemde-editor

easymde editor markdown marked marked-js react simplemde

Last synced: 29 days ago
JSON representation

React wrapper for simplemde (easymde) markdown editor

Awesome Lists containing this project

README

        

# React SimpleMDE (EasyMDE) Markdown Editor

[![NPM version][npm-badge]][npm]

React component wrapper for
[EasyMDE (the most fresh SimpleMDE fork)](https://github.com/Ionaru/easy-markdown-editor).

Only two dependencies, React (peer) and EasyMDE (peer).

Built by [@RIP21](https://twitter.com/rip212) 👨‍💻

- [New in v5](#new-in-v5)
- [Install](#install)
- [Demo](#demo)
- [Usage](#usage)
- [Controlled usage](#controlled-usage)
- [Options](#options)
- [Hotkeys](#hotkeys)
- [Custom preview rendering example](#custom-preview-rendering-example)
- [Events / Additional event listeners for events of CodeMirror](#events--additional-event-listeners-for-events-of-codemirror)
- [Autosaving](#autosaving)
- [Retrieve `easymde`, `codemirror` or `cursor` info to be able to manipulate it.](#retrieve-easymde-codemirror-or-cursor-info-to-be-able-to-manipulate-it)
- [Basic testing](#basic-testing)
- [API](#api)
- [Props](#props)
- [All exports list](#all-exports-list)
- [Changelog](#changelog)
- [New in v4](#new-in-v4)
- [New in v3](#new-in-v3)
- [New in v2](#new-in-v2)

Table of contents generated with markdown-toc

## New in v5

- [breaking] Full rewrite to hooks. Means more reactive so, probably, less bugs related with updates. Minimum React version required `>=16.8.2`
- [breaking] `easymde` now a peer dependency, please install it manually
- [breaking] `label` prop has been removed
- [breaking] SSR safe nets removed, please make sure to import it dynamically
- [breaking] `options` shall be memoized to prevent new instances from being created on each render and other related to that bugs (more on that below)
- [potentially-breaking] Forwards `ref`, so you can easily get access to `div` wrapper by using `ref` prop.
- [potentially-breaking] Lots of bugs fixed, examples updated
- [potentially-breaking] `@babel/runtime` helpers are no longer inlined but imported.

## Install

```
npm install --save react-simplemde-editor easymde
```

Note: Possibly you may need to install `@babel/runtime`, try without it, but if you don't have any issues, then you shouldn't.

## Demo

[Hosted demo](https://react-simplemde-edtior.netlify.com/)

or to see it locally:

```
git clone https://github.com/RIP21/react-simplemde-editor.git
cd react-simplemde-editor
yarn install
yarn demo
open browser at localhost:3000
```

## Usage

View the [demo code](https://github.com/rip21/react-simplemde-editor/tree/master/src/demo) for more examples.

All examples below are in TypeScript

Uncontrolled usage

```tsx
import React from "react";
import SimpleMDE from "react-simplemde-editor";
import "easymde/dist/easymde.min.css";

;
```

### Controlled usage

```tsx
export const ControlledUsage = () => {
const [value, setValue] = useState("Initial value");

const onChange = useCallback((value: string) => {
setValue(value);
}, []);

return ;
};
```

### Options

You can set API of [SimpleMDE options](https://github.com/Ionaru/easy-markdown-editor#configuration) which you pass down as a `options` prop.
If you're using TypeScript it will be inferred by compiler.

Note: if you don't specify a custom id it will automatically generate an id for you.

Note that you need to `useMemo` to memoize `options` so they do not change on each rerender! It will affect behavior and performance
because then on each render of the parent that renders `SimpleMdeReact` you'll get a new instance of the editor, which you definitely want to avoid!
Also, if you change `options` on each `value` change you will lose focus.
So, put `options` as a `const` outside of the component, or if `options` shall be partially or fully set by `props` make sure to `useMemo` in
case of functional/hooks components, or class field for `class` based components.
Slightly more on that here: [#164](https://github.com/RIP21/react-simplemde-editor/issues/164)

```tsx
export const UsingOptions = () => {
const [value, setValue] = useState("Initial");

const onChange = useCallback((value: string) => {
setValue(value);
}, []);

const autofocusNoSpellcheckerOptions = useMemo(() => {
return {
autofocus: true,
spellChecker: false,
} as SimpleMDE.Options;
}, []);

return (

);
};
```

### Hotkeys

You can include key maps using the `extraKeys` prop.
Read more at [CodeMirror extra keys](https://codemirror.net/doc/manual.html#option_extraKeys)

```tsx
export const UpdateableByHotKeys = () => {
const extraKeys = useMemo(() => {
return {
Up: function (cm) {
cm.replaceSelection(" surprise. ");
},
Down: function (cm) {
cm.replaceSelection(" surprise again! ");
},
};
}, []);

const [value, setValue] = useState("initial");
const onChange = (value: string) => setValue(value);

return (

);
};
```

### Custom preview rendering example

```tsx
import ReactDOMServer from "react-dom/server";

export const CustomPreview = () => {
const customRendererOptions = useMemo(() => {
return {
previewRender() {
return ReactDOMServer.renderToString(

);
},
} as SimpleMDE.Options;
}, []);

return (


Custom preview




);
};
```

### Events / Additional event listeners for events of CodeMirror

See full list of events [here](https://codemirror.net/doc/manual.html#events)

```tsx
import { SimpleMdeReact } from "react-simplemde-editor";
import type { SimpleMdeToCodemirrorEvents } from "react-simplemde-editor";

export const CustomEventListeners = () => {
const [value, setValue] = useState("Initial value");

const onChange = useCallback((value: string) => {
setValue(value);
}, []);

// Make sure to always `useMemo` all the `options` and `events` props to ensure best performance!
const events = useMemo(() => {
return {
focus: () => console.log(value),
} as SimpleMdeToCodemirrorEvents;
}, []);

return ;
};
```

### Autosaving

```tsx
export const Autosaving = () => {
const delay = 1000;
const autosavedValue = localStorage.getItem(`smde_demo`) || "Initial value";
const anOptions = useMemo(() => {
return {
autosave: {
enabled: true,
uniqueId: "demo",
delay,
},
};
}, [delay]);

return (

);
};
```

### Retrieve `easymde`, `codemirror` or `cursor` info to be able to manipulate it.

```tsx
export const GetDifferentInstances = () => {
// simple mde
const [simpleMdeInstance, setMdeInstance] = useState(null);

const getMdeInstanceCallback = useCallback((simpleMde: SimpleMDE) => {
setMdeInstance(simpleMde);
}, []);

useEffect(() => {
simpleMdeInstance &&
console.info("Hey I'm editor instance!", simpleMdeInstance);
}, [simpleMdeInstance]);

// codemirror
const [codemirrorInstance, setCodemirrorInstance] = useState(
null
);
const getCmInstanceCallback = useCallback((editor: Editor) => {
setCodemirrorInstance(editor);
}, []);

useEffect(() => {
codemirrorInstance &&
console.info("Hey I'm codemirror instance!", codemirrorInstance);
}, [codemirrorInstance]);

// line and cursor
const [lineAndCursor, setLineAndCursor] = useState(null);

const getLineAndCursorCallback = useCallback((position: Position) => {
setLineAndCursor(position);
}, []);

useEffect(() => {
lineAndCursor &&
console.info("Hey I'm line and cursor info!", lineAndCursor);
}, [lineAndCursor]);

return (


Getting instance of Mde and codemirror and line and cursor info




);
};
```

### Basic testing

Here is how you do it. It requires mock of certain browser pieces to work, but this is whole example.

```tsx
import { act, render, screen } from "@testing-library/react";
import { useState } from "react";
import { SimpleMdeReact } from "react-simplemde-editor";
import userEvent from "@testing-library/user-event";

// @ts-ignore
Document.prototype.createRange = function () {
return {
setEnd: function () {},
setStart: function () {},
getBoundingClientRect: function () {
return { right: 0 };
},
getClientRects: function () {
return {
length: 0,
left: 0,
right: 0,
};
},
};
};

const Editor = () => {
const [value, setValue] = useState("");
return ;
};

describe("Renders", () => {
it("succesfully", async () => {
act(() => {
render();
});
const editor = await screen.findByRole("textbox");
userEvent.type(editor, "hello");
expect(screen.getByText("hello")).toBeDefined();
});
});
```

## API

### Props

```tsx
export interface SimpleMDEReactProps
extends Omit, "onChange"> {
id?: string;
onChange?: (value: string, changeObject?: EditorChange) => void;
value?: string;
extraKeys?: KeyMap;
options?: SimpleMDE.Options;
events?: SimpleMdeToCodemirrorEvents;
getMdeInstance?: GetMdeInstance;
getCodemirrorInstance?: GetCodemirrorInstance;
getLineAndCursor?: GetLineAndCursor;
placeholder?: string;
textareaProps?: Omit<
React.HTMLAttributes,
"id" | "style" | "placeholder"
>;
}
```

### All exports list

`default` - SimpleMdeReact


`SimpleMdeReact` - same as `default` but named


**Types:**


`SimpleMdeReactProps` - props of the component


`DOMEvent` - certain events that are used to get events exported below


`CopyEvents` - only copy codemirror events


`GlobalEvents` - some other global codemirror events


`DefaultEvent` - default codemirror event handler function


`IndexEventsSignature` - index signature that expects string as key and returns `DefaultEvent`


`SimpleMdeToCodemirrorEvents` - manually crafted events (based off `@types/[email protected]` that `easymde` uses internally) +
all the above merged together into whole mapping between Codemirror event names and actual handlers for
`events` prop


`GetMdeInstance` - signature of the callback function that retrieves mde instance


`GetCodemirrorInstance` - signature of the callback function that retrieves codemirror instance


`GetLineAndCursor` - signature of the callback function that retrieves line and cursor info

## Changelog

## New in v4

- Now uses [EasyMDE (the most fresh SimpleMDE fork)](https://github.com/Ionaru/easy-markdown-editor)
instead of `simplemde` itself. Possible breaking changes, so I bumped version to v4.
- One obvious breaking change. Is how CSS is have to be imported. It used to be `simplemde/dist/simplemde.min.css` now it will be `easymde/dist/easymde.min.css`

## New in v3

- The `initialValue` prop has been removed and replaced with a `value` prop, allowing direct changes to the value to be made after the component mounts.
- v3.6.8 if rendering server-side, you can set static ids to avoid errors in rendering synchronization.
- v3.6.17 TypeScript typings added.
- v3.6.19 All props will be passed to the wrapper now (except a id, onChange and few others that are ignored)
- v3.6.21 React 17 support (UNSAFE methods are no longer used)

## New in v2

Version 1.0 did not have SimpleMDE options configured well, this readme reflects the changes made to better include options.
This is still a very new project. Testing, feedback and PRs are welcome and appreciated.

[npm-badge]: http://badge.fury.io/js/react-simplemde-editor.svg
[npm]: http://badge.fury.io/js/react-simplemde-editor