Building a Rich Text Editor with React and Draft.js, Part 2.4: Embedding Images

yarn add draft-js

Overview of Relevant Draft.js API Building Blocks

Custom Block Components

Demo: Building the Image Embedding Functionality

Defining a Custom Renderer Object

import React from "react";
import { EditorState, RichUtils, AtomicBlockUtils } from "draft-js";
export const mediaBlockRenderer = block => {
if (block.getType() === "atomic") {
return {
component: Media,
editable: false
};
}
return null;
};
  • Check if the block passed in as an argument has type atomic
  • Indicate the respective component to render (in this example, we’ll be rendering Media, which we will build in a sec)
  • Pass the optional props object includes props that will be passed through to the rendered custom component via the props.blockProps sub property object
  • Define the editable property asfalse since the component is will not include text content
{...}
import { mediaBlockRenderer } from "./entities/mediaBlockRenderer";
class PageContainer extends React.Component {{...}render() {
return (
<Editor

{...}
ref="editor"
blockRendererFn={mediaBlockRenderer} />
);
}
}
export default PageContainer;

Building the Media Custom Block Component and Image Entity

const Image = props => {
if (!!props.src) {
return <img src={props.src} />;
}
return null;
};
const Media = props => {
const entity = props.contentState.getEntity(props.block.getEntityAt(0));
const { src } = entity.getData();
const type = entity.getType();

let media;

if (type === "image") {
media = <Image src={src} />;
}

return media;
};

Building addImage()

  1. First, add AtomicBlockUtils to our list of imports in the PageContainer component:
import { EditorState, RichUtils, AtomicBlockUtils } from “draft-js”;
focus = () => this.refs.editor.focus();
onAddImage = (e) => {
e.preventDefault();
const editorState = this.state.editorState;
const urlValue = window.prompt("Paste Image Link");
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(
"image",
"IMMUTABLE",
{ src: urlValue }
);
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newEditorState = EditorState.set(
editorState,
{ currentContent: contentStateWithEntity },
"create-entity"
);
this.setState(
{
editorState: AtomicBlockUtils.insertAtomicBlock(
newEditorState,
entityKey,
" "
)
},
() => {
setTimeout(() => this.focus(), 0);
}
);
};
  • The user is prompted to input the url source of an image
  • An entity is created with type (“image”), mutability (“IMMUTABLE”), and data ({src: url})
  • Using the key of the newly created entity, we can then update our the EditorState
  • We’ll call this.focus() once our EditorState has finished updating. This way, users will be able to immediately resume entering (or deleting) text upon the addition of the image¹.

Building ‘Add Image’ UI Button

<button className="inline styleButton" onClick={this.onAddImage}>
<i class="material-icons">image</i>
</button>
Demo Code is available here: https://codesandbox.io/embed/n0ozyqr9z4

But Siobhan, aren’t you breaking the declarative paradigm by using a ref to call focus() directly on the Editor component?

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store