Home

Awesome

React SimpleMDE (EasyMDE) Markdown Editor

NPM version

React component wrapper for EasyMDE (the most fresh SimpleMDE fork).

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

Built by @RIP21 👨‍💻

<small><i><a href='http://ecotrust-canada.github.io/markdown-toc/'>Table of contents generated with markdown-toc</a></i></small>

New in v5

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

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 for more examples.

All examples below are in TypeScript

Uncontrolled usage

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

<SimpleMDE />;

Controlled usage

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

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

  return <SimpleMdeReact value={value} onChange={onChange} />;
};

Options

You can set API of SimpleMDE options 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

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 (
    <SimpleMdeReact
      options={autofocusNoSpellcheckerOptions}
      value={value}
      onChange={onChange}
    />
  );
};

Hotkeys

You can include key maps using the extraKeys prop. Read more at CodeMirror extra keys

export const UpdateableByHotKeys = () => {
  const extraKeys = useMemo<KeyMap>(() => {
    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 (
    <SimpleMdeReact value={value} onChange={onChange} extraKeys={extraKeys} />
  );
};

Custom preview rendering example

import ReactDOMServer from "react-dom/server";

export const CustomPreview = () => {
  const customRendererOptions = useMemo(() => {
    return {
      previewRender() {
        return ReactDOMServer.renderToString(
          <ReactMarkdown
            source={text}
            renderers={{
              CodeBlock: CodeRenderer,
              Code: CodeRenderer,
            }}
          />
        );
      },
    } as SimpleMDE.Options;
  }, []);

  return (
    <div>
      <h4>Custom preview</h4>
      <SimpleMdeReact options={customRendererOptions} />
    </div>
  );
};

Events / Additional event listeners for events of CodeMirror

See full list of events here

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 <SimpleMdeReact events={events} value={value} onChange={onChange} />;
};

Autosaving

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 (
    <SimpleMdeReact id="demo" value={autosavedValue} options={anOptions} />
  );
};

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

export const GetDifferentInstances = () => {
  // simple mde
  const [simpleMdeInstance, setMdeInstance] = useState<SimpleMDE | null>(null);

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

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

  // codemirror
  const [codemirrorInstance, setCodemirrorInstance] = useState<Editor | null>(
    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<Position | null>(null);

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

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

  return (
    <div>
      <h4>Getting instance of Mde and codemirror and line and cursor info</h4>
      <SimpleMdeReact
        value="Go to console to see stuff logged"
        getMdeInstance={getMdeInstanceCallback}
        getCodemirrorInstance={getCmInstanceCallback}
        getLineAndCursor={getLineAndCursorCallback}
      />
    </div>
  );
};

Basic testing

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

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 <SimpleMdeReact value={value} onChange={setValue} />;
};

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

API

Props

export interface SimpleMDEReactProps
  extends Omit<React.HTMLAttributes<HTMLDivElement>, "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<HTMLTextAreaElement>,
    "id" | "style" | "placeholder"
  >;
}

All exports list

default - SimpleMdeReact <br><br> SimpleMdeReact - same as default but named<br><br> Types: <br><br> SimpleMdeReactProps - props of the component <br><br> DOMEvent - certain events that are used to get events exported below <br><br> CopyEvents - only copy codemirror events <br><br> GlobalEvents - some other global codemirror events <br><br> DefaultEvent - default codemirror event handler function <br><br> IndexEventsSignature - index signature that expects string as key and returns DefaultEvent <br><br> SimpleMdeToCodemirrorEvents - manually crafted events (based off @types/codemirror@0.0.109 that easymde uses internally) + all the above merged together into whole mapping between Codemirror event names and actual handlers for events prop <br><br> GetMdeInstance - signature of the callback function that retrieves mde instance <br><br> GetCodemirrorInstance - signature of the callback function that retrieves codemirror instance <br><br> GetLineAndCursor - signature of the callback function that retrieves line and cursor info <br><br>

Changelog

New in v4

New in v3

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.