Contribute to this guideReport an issue

CKEditor 4 WYSIWYG Editor React Integration Documentation

The React integration allows you to implement CKEditor 4 as a React component, using the <CKEditor /> JSX tag or useCKEditor hook.

The following examples showcase the most important features of the CKEditor 4 WYSIWYG editor React integration.

Click the tab below to change an example.

Get Sample Source Code

  • Classic editor
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { CKEditor } from 'ckeditor4-react';
    
    ReactDOM.render(
    	<CKEditor
    		initData={<p>This is a CKEditor 4 WYSIWYG editor instance created by ️⚛️ React.</p>}
    	/>,
    	document.getElementById( 'app' )
    );
  • Inline editor
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { CKEditor } from 'ckeditor4-react';
    
    ReactDOM.render(
    	<CKEditor
    		type="inline"
    		initData={<p>This is a CKEditor 4 WYSIWYG editor instance created by ️⚛️ React.</p>}
    	/>,
    	document.getElementById( 'app' )
    );
  • Editor with custom event handlers and configuration
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { CKEditor } from 'ckeditor4-react';
    
    const { useState } = React;
    
    const ConfigEvents = () => {
    	const [ events, setEvents ] = useState( [] );
    
    	const logEvent = ( evt ) => {
    		evt.timestamp = new Intl.DateTimeFormat( 'en', {
    			hour12: false,
    			hour: '2-digit',
    			minute: '2-digit',
    			second: '2-digit'
    		} ).format( new Date() );
    
    		setEvents( events =>  [ evt, ...events ] );
    	}
    
    	const clearEvents = () => {
    		setEvents( [] );
    	}
    
    	return (
    		<div>
    			<h2>WYSIWYG editor with custom event handlers and configuration</h2>
    			<p>
    				Editors created with the CKEditior 4 React component are highly customizable. It is possible to overwrite every configuration setting using the <code>config</code> property and passing an object containing the configuration to it.
    			</p>
    			<p>
    				Additionally, the CKEditor 4 WYSIWYG editor component for React allows you to bind any event handler using properties with names starting with <code>on</code>, followed by the name of the event with the first letter capitalized. The following example shows how to bind several common CKEditor 4 events and apply custom toolbar configuration.
    			</p>
    			<CKEditor
    				initData="This is a CKEditor 4 WYSIWYG editor instance created by ️⚛️ React."
    				config={{
    					toolbar: [
    						[ 'Source' ],
    						[ 'Styles', 'Format', 'Font', 'FontSize' ],
    						[ 'Bold', 'Italic' ],
    						[ 'Undo', 'Redo' ],
    						[ 'EasyImageUpload' ],
    						[ 'About' ]
    					],
    					extraPlugins: 'easyimage',
    					removePlugins: 'image',
    					cloudServices_uploadUrl: 'https://33333.cke-cs.com/easyimage/upload/',
    					cloudServices_tokenUrl:
    						'https://33333.cke-cs.com/token/dev/ijrDsqFix838Gh3wGO3F77FSW94BwcLXprJ4APSp3XQ26xsUHTi0jcb1hoBt'
    				}}
    				onFocus={logEvent}
    				onBlur={logEvent}
    				onChange={logEvent}
    				onSelectionChange={logEvent}
    			/>
    			<h3>Events Log</h3>
    			<small>To check additional details about every event, consult the console in the browser developer tools.</small>
    			<EventLog stream={events} />
    			<button onClick={clearEvents}>Clear events log</button>
    		</div>
    	);
    }
    
    const EventLog = ( { stream } ) => {
    	return (
    		<div className="event-log">
    			<ul className="event-log__events">
    				{
    					stream.map( event => {
    						return (
    							<li className="event-log__event">
    								<Event data={event} />
    							</li>
    						)
    					} )
    				}
    			</ul>
    		</div>
    	);
    }
    
    const Event = ( { data: { name, timestamp } } ) => {
    	return (
    		<>
    			{timestamp} – {name}
    		</>
    	);
    }
    
    ReactDOM.render( <ConfigEvents />, document.getElementById( 'app' ) );
  • Lifting state up
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { useCKEditor, CKEditorEventAction } from 'ckeditor4-react';
    
    const { useEffect, useState, useReducer, useMemo } = React;
    
    const Editor = ( { dispatch, state } ) => {
    	const [ element, setElement ] = useState();
    
    	const { editor } = useCKEditor( {
    		element,
    		dispatchEvent: dispatch,
    		subscribeTo: [ 'focus', 'change' ],
    		initContent: state.data
    	} );
    
    	/**
    	 * Invoking `editor.setData` too often might freeze the browser.
    	 */
    	const setEditorData = useMemo( () => {
    		if ( editor ) {
    			return new CKEDITOR.tools.buffers.throttle( 500, data => {
    				if ( editor ) {
    					editor.setData( data );
    				}
    			} ).input;
    		}
    	}, [ editor ] );
    
    	/**
    	 * Sets editor data if it comes from a different source.
    	 */
    	useEffect( () => {
    		if ( state.currentEditor !== 'CKEditor' && setEditorData ) {
    			setEditorData( state.data );
    		}
    	}, [ setEditorData, state ] );
    
    	return <div ref={setElement} />;
    }
    
    const TextAreaEditor = ( { dispatch, state } ) => {
    	const handleTextAreaChange = evt => {
    		const value = evt.currentTarget.value;
    		dispatch( { type: 'textareaData', payload: value } );
    	};
    
    	const handleFocus = () => {
    		dispatch( { type: 'textareaFocus' } );
    	};
    
    	return (
    		<>
    			<p>
    				<label htmlFor="editor-editor">The editor content:</label>
    			</p>
    			<p>
    				<textarea
    					id="editor-editor"
    					className="binding-editor"
    					value={state.data}
    					onChange={handleTextAreaChange}
    					onFocus={handleFocus}
    				/>
    			</p>
    		</>
    	);
    }
    
    const reducer = ( state, action ) => {
    	switch ( action.type ) {
    		case 'textareaData': {
    			return {
    				...state,
    				data: action.payload
    			};
    		}
    		case 'textareaFocus': {
    			return {
    				...state,
    				currentEditor: 'textarea'
    			};
    		}
    		case CKEditorEventAction.change: {
    			return {
    				...state,
    				data:
    					state.currentEditor === 'CKEditor' ?
    						action.payload.editor.getData() :
    						state.data
    			};
    		}
    		case CKEditorEventAction.focus: {
    			return {
    				...state,
    				currentEditor: 'CKEditor'
    			};
    		}
    	}
    }
    
    const StateLifting = () => {
    	const [ state, dispatch ] = useReducer( reducer, {
    		data: '<p>This is a CKEditor 4 WYSIWYG editor instance created by ️⚛️ React.</p>',
    		currentEditor: undefined
    	} );
    
    	return (
    		<div>
    			<h2>Lifting state up</h2>
    			<p>
    				Shared state of React components can be moved <a href="https://reactjs.org/docs/lifting-state-up.html">higher up the React tree</a>. In the example below, CKEditor 4 WYSIWYG editor component shares content with other components.
    			</p>
    			<p>
    				Additionally, <code>useCKEditor</code> hook is used to access <code>editor</code> instance directly and use its methods imperatively, e.g. <code>editor.setData( data )</code>.
    			</p>
    			<TextAreaEditor dispatch={dispatch} state={state} />
    			<div className="editor-instance">
    				<Editor dispatch={dispatch} state={state} />
    			</div>
    			<div className="editor-preview">
    				<h2>Rendered content</h2>
    				<div dangerouslySetInnerHTML={{ __html: state.data }}></div>
    			</div>
    		</div>
    	);
    }
    
    ReactDOM.render( <StateLifting />, document.getElementById( 'app' ) );