CKEditor 4 React Integration
This feature is provided through the ckeditor4-react
npm package.
The document below serves as a reference for the public API of ckeditor4-react
starting from v2.
CKEditor 4 offers a React integration through the library described below. By providing deep integration of CKEditor 4 and React we let you use the native features of the WYSIWYG editor inside your React app. The latest version of this package is compatible with React 18.
# CKEditor 4 / React compatibility
Over time several major versions of CKEditor 4 React Integration have been released. Below is a short summary of all major versions of this library and their main characteristics.
Integration Version | React Version | Summary | Supported browsers |
---|---|---|---|
v1 | >=16 <17 | Initial release. See docs here. | Supports listed browsers except for Internet Explorer 8-10. |
v2 | >=16.9 <=17 | Includes major refactoring and support for React hooks. | Supports listed browsers except for Internet Explorer 8-10. IE11 requires Promise polyfill. |
v3 | >=16.9 <=17 | Released due to breaking changes in CKEditor 4. | Supports listed browsers except for Internet Explorer 8-10. IE11 requires Promise polyfill. |
v4 | >=18 | Supports only React 18. | Supports listed browsers except for Internet Explorer. |
Although the latest version of CKEditor 4 React Integration is compatible with React 18 only, it is still possible to combine latest versions of CKEditor 4 with legacy versions of React. In order to do that, please use v3 version of this library and customize CKEditor 4 version with the help of editorUrl
prop as described here.
# Basic Usage
In order to create an editor instance in React, install the ckeditor4-react
npm package as a dependency of your project:
npm install ckeditor4-react
Note that react
is a peer dependency and ckeditor4
is an optional peer dependency.
This package exposes a high-level CKEditor
component and a low-level useCKEditor
hook. For simpler use cases CKEditor
component should be a go-to solution. For advanced cases consider using useCKEditor
hook.
An example React app featuring CKEditor
component would look like the following:
import React from 'react';
import { CKEditor } from 'ckeditor4-react';
function App() {
return (
<div className="App">
<h2>Using CKEditor 4 in React</h2>
<CKEditor
initData="<p>Hello from CKEditor 4!</p>"
onInstanceReady={ () => {
alert( 'Editor is ready!' );
} }
/>
</div>
);
}
export default App;
# Customizing CKEditor Preset or Version
By default, the CKEditor 4 React integration loads the standard-all preset of the latest CKEditor 4 release from the CDN when creating the first editor. This behavior can be altered by using the editorUrl
prop to point to the desired CKEditor script location:
// Hook
const { editor } = useCKEditor( {
...props,
editorUrl: 'https://your-website.example/ckeditor/ckeditor.js'
} );
// Component
const component = (
<CKEditor
{...props}
editorUrl="https://your-website.example/ckeditor/ckeditor.js"
/>
);
// Pair with an older version of CKEditor 4
const component = (
<CKEditor
{...props}
editorUrl="https://cdn.ckeditor.com/4.18.0/standard-all/ckeditor.js"
/>
);
Value of editorUrl
must remain constant across all instances of editor.
# CKEditor component
The CKEditor
component is a wrapper around low-level useCKEditor
hook.
# API
The component’s API exposes initial configuration options such as config
, editorUrl
, or initData
as well as editor’s event handlers. If you find CKEditor
component’s capabilities insufficient, consider using a more flexible useCKEditor
hook.
Prop | Type | Default | Description |
---|---|---|---|
config | object | {} |
Config object passed to editor’s constructor. See config details here. |
debug | bool | undefined |
Toggles debugging mode. Use it to log info related to editor events. |
editorUrl | string | link to CDN | Url to CKEditor script. See Customizing CKEditor Preset or Version. |
initData | node | undefined |
Editor’s content will be populated with initData once it’s initialized.Either string with markup or JSX can be provided. |
name | string | undefined |
A unique identifier of editor instance. See details here. |
on${EventName} |
func | undefined |
Handlers for events specified under events section are accepted as props in the form on${EventName} , e.g. to handle instanceReady event, use onInstanceReady property. Each event handler has event info passed as an argument. In addition to native editor events, component supports custom events as well, e.g. customEvent is handled with onCustomEvent property.Two more events are provided by the React integration: namespaceLoaded and beforeLoad . The first one is fired only once, when the CKEDITOR global namespace is loaded while the latter is fired right before any instance of editor is created. Both handlers receive CKEDITOR object as argument. |
readOnly | bool | undefined |
Starts editor in read-only mode. Equivalent of adding { readOnly: true } to config but takes precedence over it.Also allows to toggle editor’s read-only mode on runtime.See details here. |
style | object | undefined |
Sets editor container’s styles. Styles can be also modified on runtime. |
type | classic | inline |
classic |
Initializes editor in either classic or inline mode.See details for classic type here and for inline type here. |
const component = (
<CKEditor
config={configObject}
debug={true}
editorUrl="https://your-website.example/ckeditor/ckeditor.js"
initData="<p>Hello from CKEditor 4!</p>"
name="my-ckeditor"
onNamespaceLoaded={ CKEDITOR => {
// Handles `namespaceLoaded` event which is fired once the CKEDITOR namespace is loaded.
// This event is emitted only once.
} }
onBeforeLoad={ CKEDITOR => {
// Handles `beforeLoad` event which is fired before an editor instance is created.
} }
onInstanceReady={ ( { editor } ) => {
// Handles native `instanceReady` event.
} }
onFocus={ ( { editor } ) => {
// Handles native `focus` event.
} }
onCustomEvent={ ( { editor } ) => {
// Handles custom `customEvent` event.
} }
{...otherEventHandlers}
readOnly={false}
style={ { borderColor: 'blue' } }
type="classic"
/>
);
# Data initialization
Prop initData
accepts either a string with HTML or JSX. The samples below produce equivalent results.
import React from 'react';
import { CKEditor } from 'ckeditor4-react';
// Provide string with HTML to initialize editor
function App() {
return <CKEditor initData="<p>Hello <strong>world</strong>!</p>" />;
}
export default App;
import React from 'react';
import { CKEditor } from 'ckeditor4-react';
// Provide JSX to initialize editor
function App() {
return <CKEditor initData={<p>Hello <strong>world</strong>!</p>} />;
}
export default App;
# useCKEditor hook
The useCKEditor
hook constitutes the core of CKEditor 4 React integration. It can be used to cover more advanced use cases or build more specialized hooks and components. As a matter of fact, CKEditor
component is built on top of useCKEditor
hook.
# API
Props:
Prop | Type | Default | Description |
---|---|---|---|
config | object | {} |
Config object passed to editor’s constructor. See config details here. |
debug | bool | undefined |
Toggles debugging mode. Use it to log info related to editor events. |
dispatchEvent | func | undefined |
Dispatches editor events. Pass a custom function or dispatch from useReducer . Event type and payload will be passed as part of the first argument: ({ type, payload }) => { ... } . Event name is provided as type and its value is prefixed with __CKE__ in order to facilitate integration with useReducer . Use CKEditorEventAction helper to access prefixed event names. |
editorUrl | string | link to CDN | See Customizing CKEditor Preset or Version. |
element | object | undefined |
Required. Reference to underlying to DOM element. |
type | classic | inline |
classic |
Initializes editor in either classic or inline mode.See details for classic type here and for inline type here. |
subscribeTo | array | undefined |
List of editor events to subscribe to, e.g. [ 'focus', 'blur' ] . Accepts custom editor events. In addition, namespaceLoaded and beforeLoad can be passed as well.Only specified events will be dispatched through dispatchEvent function.If omitted, all events will be dispatched. |
Result:
Prop | Type | Default | Description |
---|---|---|---|
editor | object | undefined |
Editor instance. Defaults to undefined (before editor is initialized).See editor instance details here. |
status | unloaded | loaded | ready | destroyed |
undefined |
Current status of editor. Equivalent of editor.status .See status details here. |
error | bool | false |
Indicates that an error occurred during editor creation. This is a non-recoverable state. It indicates that something wrong happened during instantiation of editor, e.g. incorrect editorUrl or element were provided. |
loading | bool | false |
Indicates that CKEditor script is being loaded from a remote url. |
const { editor, status, error, loading } = useCKEditor( {
config: configObject,
debug: true,
dispatchEvent: ( { type, payload } ) => {
if ( type === CKEditorEventAction.instanceReady ) {
alert( 'Editor is ready!' );
}
},
editorUrl: 'https://your-website.example/ckeditor/ckeditor.js',
element: elementObject,
type: 'classic',
subscribeTo: [ 'instanceReady' ]
} );
# dispatchEvent explained
By providing dispatchEvent
function, the useCKEditor
hook’s API unlocks more powerful patterns than CKEditor
component has to offer.
For instance, pass dispatch
from useReducer
in order to listen to editor events and derive state of your components as needed.
In the example below, reducer
is used to calculate next state of the component.
import React from 'react';
import { CKEditorEventAction, useCKEditor } from 'ckeditor4-react';
function Editor( { dispatchEvent, initData } ) {
// Use `useState` rather than `useRef` in order to trigger re-render.
const [ element, setElement ] = React.useState();
const { status } = useCKEditor( {
element,
dispatchEvent,
subscribeTo: [ 'blur', 'change', 'focus' ]
} );
return (
<div
ref={setElement}
style={status !== 'ready' ? { visibility: 'hidden' } : undefined}
>
{initData}
</div>
);
}
function Feedback() {
// `dispatch` can be passed directly to `useCKEditor` hook.
const [
{ canSendFeedback, data, isUserEditing },
dispatch
] = React.useReducer( reducer, {
canSendFeedback: false,
data: undefined,
isUserEditing: false
} );
const handleClick = () => {
alert( `Feedback has been submitted successfully:\n${ data }` );
};
return (
<div>
<Editor
dispatchEvent={dispatch}
initData={<p>Let us hear your feedback!</p>}
/>
<button disabled={!canSendFeedback} onClick={handleClick}>
Send
</button>
{isUserEditing && <div>We're happy to hear your feedback!</div>}
</div>
);
}
function reducer( state, action ) {
switch ( action.type ) {
case CKEditorEventAction.change:
const data = action.payload.editor.getData().trim();
return {
...state,
canSendFeedback: !!data,
data
};
case CKEditorEventAction.focus:
return {
...state,
isUserEditing: true
};
case CKEditorEventAction.blur:
return {
...state,
isUserEditing: false
};
}
}
export default Feedback;
For more examples see repo.
# Editor initialization
Due to the nature of React functional components (including hooks rules) some props are memoized under the hood. React integration ensures referential stability of the following props: config
, debug
, type
, editorUrl
, dispatchEvent
, subscribeTo
, and all event handlers (on${EventName}
). Passing new values of these props across renders will have no impact on already created instances of CKEditor
component or useCKEditor
hook. A new component instance must be created in order to override memoized values. This can be achieved by leveraging React’s key
prop.
# Utilities
The library exposes few utilities such as CKEditorEventAction
dictionary and registerEditorEventHandler
function.
# CKEditorEventAction
Object that maps editor event names to their prefixed equivalents, e.g. instanceReady
becomes __CKE__instanceReady
. It’s useful when using useCKEditor
in combination with useReducer
.
function reducer( state, action ) {
switch ( action.type ) {
case CKEditorEventAction.change:
// calculate new state
case CKEditorEventAction.blur:
// calculate new state
}
}
See useCKEditor
hook docs for a full example.
# registerEditorEventHandler
By default all event handlers registered with the help of CKEditor
component and useCKEditor
hook get default priority. With the help of registerEditorEventHandler
function higher priorities can be assigned or listenerData
can be attached.
registerEditorEventHandler
function can also be used to register new handlers dynamically, e.g. when a prop changes.
Returns a cleanup callback to facilitate usage within useEffect
.
Prop | Type | Default | Description |
---|---|---|---|
debug | bool | undefined |
Toggles debugging mode. Use it to log info related to editor events. |
editor | object | – | Required. Instance of editor. |
evtName | string | – | Required. Name of the event for which the handler will be registered, e.g. instanceReady . |
handler | func | – | Required. Handler function to register. See more details. |
listenerData | any | undefined |
Data that will be passed to handler as listenerData .See more details. |
priority | number | undefined |
Listener priority. Lower values have higher priority. See more details. |
Example usage:
import React from 'react';
import {
useCKEditor,
CKEditorEventAction,
registerEditorEventHandler
} from 'ckeditor4-react';
function Editor( { someProp } ) {
const [ element, setElement ] = React.useState();
const { editor } = useCKEditor( {
element,
// `dispatchEvent` is memoized, so initial value of `someProp` will be always used.
dispatchEvent: ( { type } ) => {
if ( type === CKEditorEventAction.focus ) {
console.log(
`Will be called with initial value of ${ someProp }.`
);
}
},
subscribeTo: [ 'focus' ]
} );
React.useEffect( () => {
if ( editor ) {
// Registers new handler with high priority whenever value of `someProp` changes.
const cleanup = registerEditorEventHandler( {
editor,
evtName: 'focus',
handler: () => {
console.log(
`Will be called with current value of ${ someProp } before regular event handlers.`
);
},
priority: 0
} );
return cleanup;
}
}, [ editor, someProp ] );
return <div ref={setElement} />;
}
export default Editor;
# prefixEventName
Prefixes event name with __CKE__
. Useful for prefixing custom events in reducer
.
import { prefixEventName } from 'ckeditor4-react';
function reducer( state, action ) {
switch ( action.type ) {
case prefixEventName( 'myCustomEvent' ):
// calculate new state
}
}
# stripPrefix
Removes prefix from event name.
import { stripPrefix } from 'ckeditor4-react';
const evtName = stripPrefix( CKEditorEventAction.focus );
console.log( evtName === 'focus' ); // true
# TypeScript support
React integration provides TypeScript definitions for all its interfaces. Please note that this library does not provide types for CKEDITOR
namespace. Therefore, CKEDITOR
object (as well as instances of editor
, etc.) has any
type.
Only TypeScript 3.5+ is supported.
Type argument must be provided in order to use custom events.
Usage with CKEditor
component:
import * as React from 'react';
import { CKEditor, CKEditorEventHandler } from 'ckeditor4-react';
function Editor() {
return (
<CKEditor<{
onCustomEvent: CKEditorEventHandler<'customEvent'>;
}>
onCustomEvent={( { name } ) => {
console.log( name ); // 'customEvent'
}}
/>
);
}
export default Editor;
Usage with useCKEditor
hook:
import * as React from 'react';
import { useCKEditor, prefixEventName } from 'ckeditor4-react';
function Editor() {
const [ element, setElement ] = React.useState<HTMLDivElement | null>( null );
useCKEditor<'customEvent' | 'anotherCustomEvent'>( {
element,
dispatchEvent: ( { type } ) => {
if ( type === prefixEventName( 'customEvent' ) ) {
console.log( type ); // '__CKE__customEvent'
} else {
console.log( type ); // '__CKE__anotherCustomEvent'
}
},
subscribeTo: [ 'customEvent', 'anotherCustomEvent' ]
} );
return <div ref={setElement} />;
}
export default Editor;
# Migration from v1
The previous version of this library provided CKEditor
component with an interface almost identical to the current one.
The most notable difference is that data
prop was replaced by initData
prop. Previously, passing a new value of data
prop would trigger a call to editor.setData
under the hood. In newer versions, data can be set only once, during editor instantiation (hence initData
). The rationale behind this change is that data
prop created false illusion of being the only source of truth for the editor’s data while in fact, the editor’s state is most of the time controlled by the editor itself.
In order to change editor’s data with the new version of the CKEditor
component, use editor.setData
imperatively within an event handler. On a side note, remember that any advanced use-case can be achieved with useCKEditor
hook as it gives you access to current editor
instance within main body of render
function.
Below is an example of previous usage of data
prop and then a counter-example of the new approach that combines initData
and an event handler.
Old v1 usage:
/* !!! Deprecated !!! */
/* !!! v1 !!! */
import React from 'react';
import CKEditor from 'ckeditor4-react';
const getArticle = id => {
return new Promise( resolve => {
setTimeout( () => {
resolve( `<p>Here is my article with id ${ id }...</p>` );
}, 5000 );
} );
};
function Article( { id } ) {
// You might be mislead by thinking that `data` is the only source of truth for editor's data.
// In fact editor's data as returned by `editor.getData()` will be usually different than `data`.
const [ data, setData ] = React.useState( 'Hello from CKEditor 4!' );
// Will be triggered whenever `id` of article changes.
React.useEffect( () => {
getArticle( id ).then( article => {
setData( article );
} );
}, [ id ] );
return <CKEditor data={data} />;
}
export default Article;
Current usage:
/* !!! Current !!! */
import React from 'react';
import { CKEditor } from 'ckeditor4-react';
const getArticle = id => {
return new Promise( resolve => {
setTimeout( () => {
resolve( `<p>Here is my article with id ${ id }...</p>` );
}, 5000 );
} );
};
function Article( { id } ) {
const handleInstanceReady = ( { editor } ) => {
// Will be triggered only once, when editor is ready for interaction.
getArticle( id ).then( article => {
editor.setData( article );
} );
};
return (
<CKEditor
initData="Hello from CKEditor 4!"
onInstanceReady={handleInstanceReady}
/>
);
}
export default Article;
Other notable differences are:
editorUrl
is now passed as a prop- library exposes
useCKEditor
hook that can be used instead ofCKEditor
component to cover more advanced use-cases
# CKEditor 4 React Integration Demo
See the working “CKEditor 4 React Integration” sample that showcases the most important features of the integration, including choosing the editor type, configuration and events, or lifting state up.