React component wrapper for EasyMDE (the most fresh SimpleMDE fork).
Only two dependencies, React (peer) and EasyMDE (peer).
Built by @RIP21 👨💻
Table of contents generated with markdown-toc
- [breaking] Full rewrite to hooks. Means more reactive so, probably, less bugs related with updates. Minimum React version required
>=16.8.2 - [breaking]
easymdenow a peer dependency, please install it manually - [breaking]
labelprop has been removed - [breaking] SSR safe nets removed, please make sure to import it dynamically
- [breaking]
optionsshall 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 todivwrapper by usingrefprop. - [potentially-breaking] Lots of bugs fixed, examples updated
- [potentially-breaking]
@babel/runtimehelpers are no longer inlined but imported.
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.
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 View the demo code for more examples.
All examples below are in TypeScript
Uncontrolled usage
importReactfrom"react";importSimpleMDEfrom"react-simplemde-editor";import"easymde/dist/easymde.min.css";<SimpleMDE/>;exportconstControlledUsage=()=>{const[value,setValue]=useState("Initial value");constonChange=useCallback((value: string)=>{setValue(value);},[]);return<SimpleMdeReactvalue={value}onChange={onChange}/>;};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
exportconstUsingOptions=()=>{const[value,setValue]=useState("Initial");constonChange=useCallback((value: string)=>{setValue(value);},[]);constautofocusNoSpellcheckerOptions=useMemo(()=>{return{autofocus: true,spellChecker: false,}asSimpleMDE.Options;},[]);return(<SimpleMdeReactoptions={autofocusNoSpellcheckerOptions}value={value}onChange={onChange}/>);};You can include key maps using the extraKeys prop. Read more at CodeMirror extra keys
exportconstUpdateableByHotKeys=()=>{constextraKeys=useMemo<KeyMap>(()=>{return{Up: function(cm){cm.replaceSelection(" surprise. ");},Down: function(cm){cm.replaceSelection(" surprise again! ");},};},[]);const[value,setValue]=useState("initial");constonChange=(value: string)=>setValue(value);return(<SimpleMdeReactvalue={value}onChange={onChange}extraKeys={extraKeys}/>);};importReactDOMServerfrom"react-dom/server";exportconstCustomPreview=()=>{constcustomRendererOptions=useMemo(()=>{return{previewRender(){returnReactDOMServer.renderToString(<ReactMarkdownsource={text}renderers={{CodeBlock: CodeRenderer,Code: CodeRenderer,}}/>);},}asSimpleMDE.Options;},[]);return(<div><h4>Custom preview</h4><SimpleMdeReactoptions={customRendererOptions}/></div>);};See full list of events here
import{SimpleMdeReact}from"react-simplemde-editor";importtype{SimpleMdeToCodemirrorEvents}from"react-simplemde-editor";exportconstCustomEventListeners=()=>{const[value,setValue]=useState("Initial value");constonChange=useCallback((value: string)=>{setValue(value);},[]);// Make sure to always `useMemo` all the `options` and `events` props to ensure best performance!constevents=useMemo(()=>{return{focus: ()=>console.log(value),}asSimpleMdeToCodemirrorEvents;},[]);return<SimpleMdeReactevents={events}value={value}onChange={onChange}/>;};exportconstAutosaving=()=>{constdelay=1000;constautosavedValue=localStorage.getItem(`smde_demo`)||"Initial value";constanOptions=useMemo(()=>{return{autosave: {enabled: true,uniqueId: "demo", delay,},};},[delay]);return(<SimpleMdeReactid="demo"value={autosavedValue}options={anOptions}/>);};exportconstGetDifferentInstances=()=>{// simple mdeconst[simpleMdeInstance,setMdeInstance]=useState<SimpleMDE|null>(null);constgetMdeInstanceCallback=useCallback((simpleMde: SimpleMDE)=>{setMdeInstance(simpleMde);},[]);useEffect(()=>{simpleMdeInstance&&console.info("Hey I'm editor instance!",simpleMdeInstance);},[simpleMdeInstance]);// codemirrorconst[codemirrorInstance,setCodemirrorInstance]=useState<Editor|null>(null);constgetCmInstanceCallback=useCallback((editor: Editor)=>{setCodemirrorInstance(editor);},[]);useEffect(()=>{codemirrorInstance&&console.info("Hey I'm codemirror instance!",codemirrorInstance);},[codemirrorInstance]);// line and cursorconst[lineAndCursor,setLineAndCursor]=useState<Position|null>(null);constgetLineAndCursorCallback=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><SimpleMdeReactvalue="Go to console to see stuff logged"getMdeInstance={getMdeInstanceCallback}getCodemirrorInstance={getCmInstanceCallback}getLineAndCursor={getLineAndCursorCallback}/></div>);};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";importuserEventfrom"@testing-library/user-event";// @ts-ignoreDocument.prototype.createRange=function(){return{setEnd: function(){},setStart: function(){},getBoundingClientRect: function(){return{right: 0};},getClientRects: function(){return{length: 0,left: 0,right: 0,};},};};constEditor=()=>{const[value,setValue]=useState("");return<SimpleMdeReactvalue={value}onChange={setValue}/>;};describe("Renders",()=>{it("succesfully",async()=>{act(()=>{render(<Editor/>);});consteditor=awaitscreen.findByRole("textbox");userEvent.type(editor,"hello");expect(screen.getByText("hello")).toBeDefined();});});exportinterfaceSimpleMDEReactPropsextendsOmit<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">;}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 DefaultEventSimpleMdeToCodemirrorEvents - 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
- Now uses EasyMDE (the most fresh SimpleMDE fork) instead of
simplemdeitself. 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.cssnow it will beeasymde/dist/easymde.min.css
- The
initialValueprop has been removed and replaced with avalueprop, 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)
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.