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 (

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) => {
const editorState = this.state.editorState;
const urlValue = window.prompt("Paste Image Link");
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(
{ src: urlValue }
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newEditorState = EditorState.set(
{ currentContent: contentStateWithEntity },
editorState: AtomicBlockUtils.insertAtomicBlock(
" "
() => {
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>
Demo Code is available here:

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