Create rich textarea component in SolidJS with CKeditor 5
Enhancing SolidJS integrate a rich textarea with CKEditor By: Dániel Sipos onSolid.js is a reactive JavaScript library for building user interfaces. It offers fine-grained reactivity and efficient rendering, making it a popular choice among developers. In this article, we explore how to integrate CKEditor, a powerful WYSIWYG editor, into a Solid.js application. The integration will involve using Solid.js’s reactivity and lifecycle hooks to create a rich text area component.
Setting Up the Project
First, ensure you have a Solid.js project setup. If not, you can create one using the Solid.js template:
npx degit solidjs/templates/ts my-solid-project
cd my-solid-project
npm install
npm start
Next, install CKEditor:
npm install @ckeditor/ckeditor5-build-classic --save
Creating the RichTextAreaField Component
We’ll create a component named RichTextAreaField that will wrap CKEditor and bind it to the Solid.js reactivity system.
import {
type JSX,
splitProps,
Show,
onMount,
createEffect,
createSignal,
} from "solid-js";
const ClassicEditor = () => import("@ckeditor/ckeditor5-build-classic");
type RichTextAreaFieldProps = {
ref: (element: HTMLTextAreaElement) => void;
name: string;
value: string | undefined;
onInput: JSX.EventHandler<HTMLTextAreaElement, InputEvent>;
onChange: JSX.EventHandler<HTMLTextAreaElement, Event>;
onBlur: JSX.EventHandler<HTMLTextAreaElement, FocusEvent>;
placeholder?: string;
required?: boolean;
class?: string;
label?: string;
error?: string;
rows?: number;
};
const editorConfig = {
toolbar: {
items: [
"undo",
"redo",
"|",
"heading",
"|",
"bold",
"italic",
"|",
"bulletedList",
"numberedList",
],
shouldNotGroupWhenFull: false,
},
};
export default function (props: RichTextAreaFieldProps) {
const [, inputProps] = splitProps(props, [
"class",
"value",
"label",
"error",
]);
const [value, setValue] = createSignal<string | undefined>(props.value);
let editorDiv!: HTMLDivElement;
let textareaRef!: HTMLTextAreaElement;
let classicEditor: any;
createEffect((prevValue) => {
if (props.value && prevValue != props.value) {
setValue(props.value);
}
return props.value;
}, props.value);
createEffect(() => {
if (classicEditor?.getData() !== value()) {
classicEditor?.setData(value());
}
});
onMount(async () => {
if (!import.meta.env.SSR) {
ClassicEditor().then((ck) => {
ck.default
.create(editorDiv, editorConfig)
.then((editor) => {
classicEditor = editor;
editor.model.document.on("change:data", () => {
const data = editor.getData();
setValue(data);
});
})
.catch((error) => {
console.error(error);
});
});
}
});
const setRef = (ref: HTMLTextAreaElement) => {
textareaRef = ref;
props.ref(ref);
};
createEffect(() => {
if (props.value !== value()) {
textareaRef.dispatchEvent(
new Event("input", { bubbles: true, cancelable: true }),
);
}
});
return (
<div class={`form-control w-full ${props.class}`}>
<div class="label">
<span class="label-text">
{props.label}
<Show when={props.required}>
<b class="ms-1 text-red-500">*</b>
</Show>
</span>
</div>
<div ref={editorDiv}></div>
<textarea
{...inputProps}
required={props.required}
value={value()}
class="textarea textarea-bordered w-full hidden"
ref={(ref) => setRef(ref)}
></textarea>
<div class="label">
<span class="label-text-alt text-error">{props.error}</span>
</div>
</div>
);
}
Explanation
- Importing Dependencies: We import the necessary functions from Solid.js and dynamically import CKEditor.
- Defining Props: The RichTextAreaFieldProps type defines the properties expected by our component, including handlers for various events and optional styling and validation properties.
- Editor Configuration: We define the toolbar configuration for CKEditor.
- Component Setup: • splitProps is used to separate class and value-related properties. • createSignal creates a reactive state for the editor’s content. • createEffect is used to update the state when the prop value changes and to synchronize the CKEditor data with the reactive state.
- Editor Initialization: onMount is used to initialize CKEditor when the component is mounted. The editor’s data change events update the reactive state.
- Ref Management: A reference to the textarea element is maintained, and an event is dispatched to notify when the content changes.
- Rendering: The component renders a label, the CKEditor container (div), and a hidden textarea that serves as the form input.
Using the Component
To use the RichTextAreaField component, simply include it in your form and pass the necessary props:
<RichTextAreaField
ref={(ref) => (this.textareaRef = ref)}
name="content"
value={this.state.content}
onInput={this.handleInput}
onChange={this.handleChange}
onBlur={this.handleBlur}
placeholder="Enter your content here..."
required
class="my-textarea"
label="Content"
error={this.state.error}
/>
This integration leverages Solid.js’s reactivity to keep the CKEditor content in sync with the component state, providing a seamless and dynamic user experience.