Setting up a rich text editor in React: Inserting images
Tags: react, javascript, tutorial, frontend
Published: 2020-10-10 02:50:27

![My text editor set up using CKEditor with image upload functionality](https://dev-to-uploads.s3.amazonaws.com/i/zie92s60scfhzwqihnfs.png)

Hello all! In my [previous post](https://dev.to/shaerins/setting-up-a-basic-rich-text-editor-in-react-3afg) I went over how to set up a very basic rich text editor using CKEditor 5. The Classic Build that I used comes with the option to allow inserting images, but not out of the box.

If you try to upload an image, you will get this warning in the console along with a link to the docs:

```
filerepository-no-upload-adapter: Upload adapter is not defined.
```

Reading the description of the error in the link, you'll see that you need to configure an 'upload adapter' in order to get usage out of the image uploading feature.

```
You need to enable an upload adapter in order to be able to upload files. This warning shows up when FileRepository is being used without definining an upload adapter.
```

In this post I'll show you how I set up my own upload adapter in order to use the image uploading feature in CKEditor.

## Set up the custom upload adapter

First I'm setting up an `UploadAdapter` class. Inside of our constructor we need to a have a file loader instance that will control the process of reading and uploading files.

``` javascript
import { firebase } from '../utilities/firebase'

class UploadAdapter {
	constructor(loader) {
		this.loader = loader
	}
}

export default UploadAdapter
```

Our upload adapter class also needs to have an `upload()` method that returns a promise that is resolved when the file is successfully uploaded. 

To make my code a little more dry, I created a variable for my firebase reference called `uploadTask`. Then I'm using `uploadTask.on()` to ultimately handle what I want to happen if the image is successfully uploaded to my firebase storage. 

In `.on()` we're passing in 4 parameters:

* a string that identifies an event (for us, we're checking if the state changed with `firebase.storage.TaskEvent.STATE_CHANGED`)
* a callback function that gives you access to a snapshot
* a second callback for error handling
* a third callback for if the file upload is successful

Here's what my `upload()` method looks like:

``` javascript
upload() {
	return this.loader.file.then(
		(file) =>
			new Promise((resolve, reject) => {
				// firebase reference
				let uploadTask = firebase.storage().ref()
					.child(`images/${file.name}`)
					.put(file)

				uploadTask.on(
						firebase.storage.TaskEvent.STATE_CHANGED,
						(snapshot) => {
							/* snapshot with info about 
							the upload progress & metadata */
						},
						(error) => {
							// error handling
						},
						() => {
							// upload successful
						}
					)
			})
	)
}
```
The snapshot contains info about the upload progress and other metadata, which can be used if you'd like to display a progress bar to the user. You can use the error callback to handle any errors that may occur during the upload process.

Finally, we want to tell firebase what to do if the image upload is successful. In our last callback function, we want to grab the download URL that was created and tell CKEditor to use that URL to display the image within the editor.

``` javascript
uploadTask.snapshot.ref
	.getDownloadURL()
	.then((downloadURL) => {
		resolve({
			default: downloadURL
		})
	})
```

Ultimately, your UploadAdapter file should end up looking like this:

``` javascript
import { firebase } from '../utilities/firebase'

class UploadAdapter {
	constructor(loader) {
		this.loader = loader
	}
	upload() {
		return this.loader.file.then(
			(file) =>
				new Promise((resolve, reject) => {
					// firebase reference
					let uploadTask = firebase.storage().ref()
						.child(`images/${file.name}`)
						.put(file)

					uploadTask.on(
						firebase.storage.TaskEvent.STATE_CHANGED,
						(snapshot) => {
							/* snapshot with info about 
							the upload progress & metadata */
						},
						(error) => {
							// error handling
						},
						() => {
							// upload successful
							uploadTask.snapshot.ref
								.getDownloadURL()
								.then((downloadURL) => {
									resolve({
										default: downloadURL
									})
								})
						}
					)
				})
		)
	}
}

export default UploadAdapter
```

## Import our custom upload adapter into our CKEditor

Import UploadAdapter into your text editor component where you are using CKEditor. The CKEditor component takes a prop called `onInit`, a function called when the editor is initialized. This gives us access to the initialized `editor`.

To specify that we want to use our own custom upload adapter, we want to utilize the `FileRepository` plugin. `FileRepository` gives us the prop `.createUploadAdapter`, which is a function that takes in a file loader instance and returns a new upload adapter instance.


``` javascript
import React, { useState } from 'react'
import CKEditor from '@ckeditor/ckeditor5-react'
import ClassicEditor from '@ckeditor/ckeditor5-build-classic'
import UploadAdapter from './UploadAdapter'

const TextEditor = ({ onSubmit }) => {
	const [body, setBody] = useState('')

	const handleSubmit = (e) => {
		e.preventDefault()
		onSubmit({ body })
	}

	return (
		<form onSubmit={handleSubmit}>
			<CKEditor
				// here's where we are using our custom adapter
				onInit={(editor) => {
					editor.plugins.get('FileRepository')
						.createUploadAdapter = (loader) => {
							return new UploadAdapter(loader)
						}
				}}
				editor={ClassicEditor}
				onChange={(event, editor) => {
					const data = editor.getData()
					setBody(data)
				}}
			/>
			<button type='submit'>Submit</button>
		</form>
	)
}

export default TextEditor
```
Now you should be allowed to upload images in your editor. Out of the box it allows you to upload, drag and paste images into the editor. It also gives you the option to let your images be inline or block, and include an image caption.

Thanks for reading! 😊