DEV Community

Md Mostafizur Rahman
Md Mostafizur Rahman

Posted on

Building a Drag-and-Drop Page Builder with React: A Complete Guide

Creating a visual page builder has become essential for modern web applications. Whether you're building a CMS, a website builder, or just want to give users more control over their content, a drag-and-drop interface can significantly improve the user experience. In this comprehensive guide, we'll walk through building a powerful page builder using the @dndbuilder.com/react package.

What We'll Build

By the end of this tutorial, you'll have a fully functional page builder that includes:

🖱️ Drag-and-drop interface for arranging content blocks
🧩 Custom blocks with editable properties
🎨 Visual controls for styling and configuration
💾 Content persistence and rendering
📱 Responsive design capabilities

Why Choose @dndbuilder.com/react?

The @dndbuilder.com/react package stands out because it offers:

  • Block-based architecture that's easy to understand and extend
  • Built-in undo/redo functionality powered by Redux
  • Responsive design tools for mobile-first development
  • Extensible API for creating custom blocks and controls
  • Server-side rendering support for production applications
  • Tree-shakable imports to keep your bundle size minimal

Getting Started

Installation

First, let's install the package and its dependencies:

npm install @dndbuilder.com/react
# or
yarn add @dndbuilder.com/react
# or
pnpm add @dndbuilder.com/react
Enter fullscreen mode Exit fullscreen mode

Quick Start

Create a basic editor component to get started:

import React, { useState } from "react";
import { Editor, BuilderProvider, store } from "@dndbuilder.com/react";
import "@dndbuilder.com/react/dist/style.css";

const editorConfig = {
  blocks: [], // We'll add blocks here
};

function App() {
  const [initialContent, setInitialContent] = useState({});

  return (
    <BuilderProvider store={store}>
      <div className="app-container">
        <Editor 
          content={initialContent} 
          builderConfig={editorConfig} 
        />
      </div>
    </BuilderProvider>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

That's it! Now, you will able to see the drag and drop editor in your application.

Understanding Core Concepts

Before diving into custom blocks, let's understand the key concepts:
1. Editor
The Editor is the main component that provides the drag-and-drop interface for building pages. It manages the state of the page content and provides tools for editing blocks.
2. Blocks
Blocks are the building units of your pages. Each block represents a specific type of content (heading, paragraph, image, etc.).
3. Controls
Controls provide the interface for users to configure block properties like text, colors, spacing, and other styling options.
4. Store
The store manages the editor's state, including content structure, selected blocks, and undo/redo history.
5. Settings
Settings contain the configuration data for each block instance.

Working with Content

Saving Content

Use the useContent hook to save content as shown in the documentation:

import { useContent } from "@dndbuilder.com/react/hooks";

function SaveButton() {
  const { content, saveContent } = useContent();

  const handleSave = async () => {
    // Save content to your backend or local storage
    try {
      await fetch("/api/save-content", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ content }),
      });
      saveContent(); // Mark content as saved
    } catch (error) {
      console.error("Failed to save content:", error);
    }
  };

  return <button onClick={handleSave}>Save Content</button>;
}
Enter fullscreen mode Exit fullscreen mode

Rendering Content

To render content on the frontend, use the RenderContent component exactly as documented:

import { RenderContent } from "@dndbuilder.com/react/components/server";
import { editorConfig } from "./editorConfig"; // Your editor configuration

async function ContentPage() {
  // Fetch content from your backend
  const response = await fetch("/api/get-content");
  const { content } = await response.json();

  return (
    <div className="page-container">
      <RenderContent content={content} builderConfig={editorConfig} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Creating Your First Custom Block

Let's create a simple block that includes a title and a description.

To create a custom block, you need to:

  1. Create a component for your block
  2. Create a control component to control block's property. (Optional)
  3. Define the block configuration using createBlockConfig utility
  4. Include the block in your editor configuration

Here's a simplified example of creating a custom block:

//my-block.tsx
import React from "react";
import { BlockProps } from "@dndbuilder.com/react/types";

const MyBlock = ({ settings, meta }: BlockProps) => {
  return (
    <div className="my-custom-block">
      <h3>{settings.title}</h3>
      <p>{settings.description}</p>

    </div>
  );
};

export default MyBlock;
Enter fullscreen mode Exit fullscreen mode

Create a control component

//my-block-control.tsx
import React from "react";
import { ControlProps } from "@dndbuilder.com/react/types";
import { TextInput } from "@dndbuilder.com/react/components";

const MyBlockControl = ({ settings, updateSettings }: ControlProps) => {
  return (
    <div className="control-panel">
      <TextInput
        label="Title"
        value={settings.title || ""}
        onChange={(value) => updateSettings({ title: value })}
      />
      <TextInput
        label="Description"
        value={settings.description || ""}
        onChange={(value) => updateSettings({ description: value })}
      />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Define your block configuration

//my-block.config.ts
import { createBlockConfig } from "@dndbuilder.com/react/utils";
import { lazy } from "react";
import { FiBox } from "react-icons/fi";

const MyBlockConfig = createBlockConfig({
  type: "my-block",
  label: "My Custom Block",
  icon: FiBox,
  component: lazy(() => import("./my-block")),
  isVisible: () => true,
  group: "Custom",
  settings: {
    title: "Default Title",
    description: "Default description text",
  },
  controls: [
    {
      label: "Content",
      component: lazy(() => import("./my-block-control")),
    },
  ],
});

export default MyBlockConfig;
Enter fullscreen mode Exit fullscreen mode

Include the block in your editor configuration

import MyBlockConfig from "./blocks/my-block/my-block.config";

export const editorConfig = {
  blocks: [
    MyBlockConfig,
    // Other blocks...
  ],
};
Enter fullscreen mode Exit fullscreen mode

Overriding Existing Blocks

You can override existing blocks by extending their configuration, as shown in the documentation:

import { BlockType } from "@dndbuilder.com/react/types";
import { createBlockConfig } from "@dndbuilder.com/react/utils";
import { lazy } from "react";

// Override the Heading block configuration
const CustomHeadingConfig = createBlockConfig({
  type: BlockType.HEADING, // Use the existing block type
  component: lazy(() => import("./components/custom-heading.block")),
  // Override other properties as needed
  controls: [
    {
      label: "Custom Style",
      component: lazy(() => import("./components/custom-heading-style.control")),
    },
  ],
});

// Include the overridden block in your editor configuration
export const editorConfig = {
  blocks: [
    CustomHeadingConfig,
    // Other blocks...
  ],
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

The @dndbuilder.com/react package provides a robust foundation for building drag-and-drop page builders. By following the patterns and examples from this article, you can create powerful, customizable page builders that provide excellent user experiences.
The key is to start with the basic setup, understand the core concepts, and gradually build complexity by adding custom blocks and advanced features. The package's extensible architecture makes it easy to scale from simple use cases to complex, production-ready applications.

Next Steps

Top comments (0)