DEV Community

Anh Tiến Nguyễn
Anh Tiến Nguyễn

Posted on

Overview about Craftjs - library used for page builder

Building rich, customizable user interfaces is a challenge that many web applications face. Developers need solutions that provide flexibility, performance, and extensibility, especially for drag-and-drop editors, landing page builders, and WYSIWYG content creation. Enter Craft.js, a headless framework for building these complex user interfaces with React. In this guide, we will learn about the architecture of Craft.js, the workflows, custom components, state management, and extensibility.

1. Introduction to Craft.js

Craft.js is a React-based headless framework for building powerful, flexible, and customizable UI editors. It offers drag-and-drop functionality, state management, and composability, giving developers complete control over how the UI looks and behaves. Unlike many out-of-the-box WYSIWYG editors, Craft.js does not provide a predefined UI or style, making it ideal for projects requiring highly tailored experiences.

Craft.js is built to give developers the tools needed to manage component trees dynamically, track component states, and manage interactive editor workflows efficiently.

2. Key Features of Craft.js

Before diving into the architecture, let's quickly revisit the core features that make Craft.js a standout choice:

  • Composable and Modular: Every part of the UI can be defined as a React component, making it modular and reusable.
  • Headless: Craft.js provides no built-in UI, which allows developers to implement custom design systems and tailor the editor to specific needs.
  • State Management: The framework manages the state of all components, keeping track of properties, layout, and interactions.
  • Drag-and-Drop Support: Components can be moved, re-arranged, and nested using drag-and-drop operations.
  • Undo/Redo Functionality: Craft.js offers built-in support for undo/redo, tracking all editor state changes.

3. Architecture Overview

Craft.js is built around a robust system of nodes, which represent components in the editor. The core elements of the Craft.js architecture are as follows:

Node System

The node system is the backbone of Craft.js. Every element rendered in the editor (whether it's text, an image, or a container) is represented as a node. Nodes are structured in a hierarchical tree, similar to the DOM tree, where each node can have parent and child nodes.

Key Node Attributes:

  • Props: Nodes carry properties such as styles, content, or layout data. These can be updated dynamically.
  • Node Type: Each node corresponds to a React component that defines how it is rendered.
  • Parent/Child Relationship: Nodes can be nested to form complex structures, allowing developers to define parent-child relationships for layouts like grids, flexboxes, etc.

Nodes are the essential building blocks of the UI, and Craft.js tracks the state of each node, allowing real-time updates and user interactions.

State Management with Nodes

The state of each node is centrally managed using React's context API. This central state allows for efficient updates, as changes to one node propagate throughout the editor without requiring manual re-renders. The state is immutable, making it easy to integrate undo/redo functionality.

Craft.js uses Redux-like state management internally, providing a centralized store for all editor operations. This means every user action, whether it's dragging a component, updating props, or re-arranging layout elements, is dispatched as an action to the store.

Rendering Engine

Craft.js features a highly efficient custom render engine. Only nodes that have been updated or modified are re-rendered, keeping the editor performant even as the complexity of the component tree grows. This architecture is ideal for applications where large numbers of nested components might exist.

Lazy Rendering: Craft.js also implements lazy rendering of components. This allows for deferment of rendering components until they are needed, reducing initial load times and improving performance on complex layouts.

Plugin and Extension System
The plugin system in Craft.js allows developers to extend the editor with custom functionality, such as adding new node types, creating new event handlers, or injecting custom UI elements into the editor interface.

4. Workflow of Craft.js

Craft.js provides several workflows that make it efficient for building UIs. These include how components are designed, how states are synchronized, and how event handling works.

Component Design Workflow

When building components in Craft.js, the key steps are:

  • Define the Component: Create a React component that represents a piece of your UI, such as a button, text box, or image.
  • Wrap the Component: Use the useNode hook to make the component editable within the Craft.js editor. This allows it to be draggable and to have its state managed.
  • Add Custom Logic: Define how the component should behave when interacted with (e.g., how props can be updated dynamically).
import React from "react";
import { useNode } from "@craftjs/core";

const Button = ({ text, backgroundColor }) => {
  const {
    connectors: { connect, drag },
  } = useNode();

  return (
    <button
      ref={(ref) => {
        connect(drag(ref))
      }}
      style={{ backgroundColor }}
    >
      {text}
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

State Synchronization Workflow

  • Initial Render: Craft.js initializes the component tree, rendering each component in its default state.
  • State Updates: When a user interacts with a component (such as dragging it to a new location or changing a property), Craft.js dispatches actions to its internal state management system.
  • Re-rendering: Only the components affected by the change are re-rendered. This ensures performance remains optimal even as the editor grows.

Event Handling Workflow

Craft.js tracks all user interactions as events. These include drag-and-drop operations, prop updates, and other UI interactions.

  • Event Dispatch: Each event is dispatched to the editor’s state management system, which then updates the appropriate node.
  • Event Listeners: Custom event listeners can be added to components to hook into specific interactions, such as selecting a component or updating its props.

5. Detailed Example with Custom Components

Let’s take the workflow of creating custom components a step further. In this example, we'll build an editable card component with text and an image that users can modify via prop controls.

Define the Card Component

import React from "react";
import { useNode } from "@craftjs/core";

const Card = ({ imageSrc, title, description }) => {
  const { connectors: { connect, drag }, actions: { setProp } } = useNode();

  return (
    <div
      ref={(ref) => {
        connect(drag(ref))
      }}
      style={{ border: '1px solid #ccc', padding: '20px' }}
    >
      <img src={imageSrc} alt={title} style={{ width: '100%', height: 'auto' }} />
      <h3>{title}</h3>
      <p>{description}</p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Add Card to the Editor

Now we add the Card component to our editor by wrapping it with the Element component from Craft.js:

import React from "react";
import { Editor, Frame, Element } from "@craftjs/core";
import Card from "./components/Card";

const App = () => {
  return (
    <Editor>
      <Frame>
        <Element canvas>
          <Card imageSrc="https://via.placeholder.com/150" title="Sample Card" description="This is a description." />
        </Element>
      </Frame>
    </Editor>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

This example creates an editable card with the ability to update the image, title, and description dynamically.

6. Advanced Customization and Extensibility

Craft.js allows developers to go beyond the basic functionality of dragging and dropping components. Here are some advanced customization techniques:

Building Custom Prop Editors

To create a fully interactive WYSIWYG editor, you'll often need custom controls that allow users to adjust component properties dynamically.

import { useNode } from "@craftjs/core";

const TextEditor = () => {
  const { actions, selectedNode } = useEditor((state) => ({
    selectedNode: state.nodes[state.events.selected],
  }));

  return (
    <div>
      <label>Font Size:</label>
      <input
        type="number"
        value={selectedNode?.data.props.fontSize || 16}
        onChange={(e) => actions.setProp(selectedNode.id, (props) => props.fontSize = e.target.value)}
      />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

7. Craft.js Workflows

Below is a flowchart illustrating how Craft.js processes events and renders components:

Craftjs workflow

Event Workflow

Creating Components:

  • Users can add components from a toolbox onto the canvas.
  • The Editor manages the state, creating a new node for each component.

Manipulating Components:

  • Users can drag and drop components, which updates the node tree in the Editor State.
  • Properties of components can be edited in real-time, thanks to the state management.

Rendering:

  • The Frame takes the current node structure and renders it as a visual representation in the Canvas.
  • The nodes update their rendering based on changes made through the editor.

Interacting:

  • Connectors allow components to be interacted with directly (e.g., resizing, moving).
  • The editor keeps track of interactions, making it possible to implement features like undo/redo.

8. Best Practices for Working with Craft.js

  • Component Granularity: Break components down into small, reusable parts. This enhances performance and keeps the state manageable.
  • Efficient Prop Handling: Ensure that prop updates are efficient to avoid unnecessary re-renders.
  • Avoid Deep Nesting: While Craft.js handles nested components well, avoid overly complex hierarchies to ensure performance remains optimal.

9. Conclusion

Craft.js is a powerful framework for building customizable UI editors that gives developers unparalleled flexibility and control over how the UI is structured. Whether you’re creating a page builder, email editor, or custom design tool, Craft.js provides all the building blocks necessary to manage complex layouts, states, and interactions efficiently.

By mastering its node system, event workflows, and state management, developers can leverage Craft.js to build scalable, performant editors tailored to their needs.

Top comments (0)