BLOG.siposdani87

Create rich textarea component in SolidJS with CKeditor 5

Enhancing SolidJS integrate a rich textarea with CKEditor
By: Dániel Sipos on

Solid.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

  1. Importing Dependencies: We import the necessary functions from Solid.js and dynamically import CKEditor.
  2. Defining Props: The RichTextAreaFieldProps type defines the properties expected by our component, including handlers for various events and optional styling and validation properties.
  3. Editor Configuration: We define the toolbar configuration for CKEditor.
  4. 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.
  5. Editor Initialization: onMount is used to initialize CKEditor when the component is mounted. The editor’s data change events update the reactive state.
  6. Ref Management: A reference to the textarea element is maintained, and an event is dispatched to notify when the content changes.
  7. 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.

Share with your friends

Related posts