Integration Options
React Integration
The Ethereum Comments Protocol provides comprehensive React integration options for both displaying and creating comments. This guide covers two main approaches:
- React Component Library - For displaying and embedding comments
- React Editor - For creating and editing comments with rich text capabilities
React Component Library
The React component library provides pre-built, customizable UI components for seamless integration into your React applications. This is the recommended approach for most web applications that need to display comments.
Demo
Installation
npm install @ecp.eth/sdk
# install peer dependencies
npm install react react-dom viem wagmi @tanstack/react-query @tanstack/query-core
Usage
import React from "react";
import { CommentsEmbed } from "@ecp.eth/sdk/embed";
export function Comments() {
return (
<div>
<h3>Comments</h3>
<CommentsEmbed
uri={
new URL(
window.location.pathname,
// replace with your url
"https://demo.ethcomments.xyz",
)
}
theme={{
mode: "light",
// ...
}}
/>
</div>
);
}
- Please note that if you are rendering the
CommentsEmbed
with server-side rendering (SSR), you do not have access to thewindow
object. Instead, you may want to construct theuri
out of theHttpRequest
object of your server-side framework. - Please also read the
targetUri
carefully in Comment Data for choosing an appropriateuri
.
API reference
CommentsEmbed
CommentsEmbedProps
EmbedConfigThemeSchema
- detailed theme customization options
React Editor
The React Editor provides a powerful, customizable rich text editor built on top of TipTap that supports mentions, file uploads, and seamless integration with the Ethereum Comments Protocol. This is the easiest way to add comment creation capabilities to your React or Next.js application.
Features
- Rich Text Editing: Built on TipTap with support for paragraphs, links, and formatting
- Mentions: Support for ENS, Farcaster, and ERC-20 token mentions with autocomplete
- File Uploads: Drag-and-drop file uploads with support for images, videos, and documents
- Reference Extraction: Extract structured references from editor content
- Content Parsing: Parse plain text with references back into rich content
- Customizable Components: Fully customizable media components and themes
- TypeScript Support: Full TypeScript support with comprehensive type definitions
Installation
npm install @ecp.eth/react-editor
# install peer dependencies
npm install @tanstack/react-query pinata react viem
Quick Start
import React from "react";
import { Editor } from "@ecp.eth/react-editor/editor";
import {
useIndexerSuggestions,
usePinataUploadFiles,
} from "@ecp.eth/react-editor/hooks";
function CommentEditor() {
const suggestions = useIndexerSuggestions();
const uploads = usePinataUploadFiles({
pinataGatewayUrl: "https://api.pinata.cloud",
generateUploadUrl: async (filename) => {
// Implement your own upload URL generation logic
return `https://api.pinata.cloud/files/${filename}`;
},
});
return (
<Editor
placeholder="Write your comment..."
suggestions={suggestions}
uploads={uploads}
onBlur={() => console.log("Editor lost focus")}
/>
);
}
Core Components
Editor
The main editor component with full rich text editing capabilities.
import React from "react";
import { Editor, type EditorRef } from "@ecp.eth/react-editor/editor";
import type {
EditorSuggestionsService,
UploadFilesService,
} from "@ecp.eth/react-editor/types";
import { useRef } from "react";
declare const suggestionsService: EditorSuggestionsService;
declare const uploadsService: UploadFilesService;
function CommentEditor() {
const editorRef = useRef<EditorRef>(null);
return (
<Editor
ref={editorRef}
placeholder="Write your comment..."
suggestions={suggestionsService}
uploads={uploadsService}
autoFocus={true}
onBlur={() => console.log("Editor lost focus")}
onEscapePress={() => console.log("Escape pressed")}
/>
);
}
Hooks
useIndexerSuggestions
Provides suggestions for ENS, Farcaster, and ERC-20 mentions using the ECP Indexer.
import { useIndexerSuggestions } from "@ecp.eth/react-editor/hooks";
const suggestions = useIndexerSuggestions();
usePinataUploadFiles
Provides file upload functionality using Pinata IPFS service.
import { usePinataUploadFiles } from "@ecp.eth/react-editor/hooks";
const uploads = usePinataUploadFiles({
pinataGatewayUrl: "some.pinata.gateway.url",
generateUploadUrl: async (filename) => {
// Implement your own upload URL generation logic
return `https://api.pinata.cloud/files/${filename}`;
},
});
useHandleDefaultEditorValue
Handles setting default editor values with content and references.
import { useHandleDefaultEditorValue } from "@ecp.eth/react-editor/hooks";
const content = useHandleDefaultEditorValue("some content", []);
Utilities
extractReferences
Extract structured references from editor content.
import type { EditorRef } from "@ecp.eth/react-editor/editor";
import { extractReferences } from "@ecp.eth/react-editor/extract-references";
declare const editorRef: React.RefObject<EditorRef>;
const editorContent = editorRef.current!.editor!.getJSON();
const references = extractReferences(editorContent);
parse
Parse plain text with references back into rich content.
import type { IndexerAPICommentReferencesSchemaType } from "@ecp.eth/sdk/indexer/schemas";
import { parse } from "@ecp.eth/react-editor/parser";
declare const plainText: string;
declare const references: IndexerAPICommentReferencesSchemaType;
const richContent = parse(plainText, references);
Configuration
File Upload Limits
// Default limits
const ALLOWED_UPLOAD_MIME_TYPES = [
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
"video/mp4",
"video/webm",
"video/avi",
"video/quicktime",
];
const MAX_UPLOAD_FILE_SIZE = 1024 * 1024 * 10; // 10MB
Custom Media Components
You can customize how media files are displayed:
import React from "react";
import { Editor } from "@ecp.eth/react-editor/editor";
import type {
UploadTrackerImageComponent,
UploadTrackerVideoComponent,
UploadTrackerFileComponent,
UploadFilesService,
EditorSuggestionsService,
} from "@ecp.eth/react-editor/types";
declare const CustomImageComponent: UploadTrackerImageComponent;
declare const CustomVideoComponent: UploadTrackerVideoComponent;
declare const CustomFileComponent: UploadTrackerFileComponent;
declare const uploadsService: UploadFilesService;
declare const suggestionsService: EditorSuggestionsService;
<Editor
imageComponent={CustomImageComponent}
videoComponent={CustomVideoComponent}
fileComponent={CustomFileComponent}
uploads={uploadsService}
suggestions={suggestionsService}
placeholder="Write your comment..."
// ... other props
/>;
Advanced Usage
Custom Suggestions Service
import type { EditorSuggestionsService } from "@ecp.eth/react-editor/types";
const customSuggestions: EditorSuggestionsService = {
search: async (query: string) => {
// Implement your own search logic
return {
results: [
{
type: "ens",
address: "0x...",
name: "example.eth",
value: "0x...",
avatarUrl: null,
url: "https://app.ens.domains/example.eth",
},
{
type: "farcaster",
fid: 0,
username: "example",
address: "0x...",
fname: "example",
value: "0x...",
avatarUrl: null,
url: "https://app.farcaster.xyz/example",
},
],
};
},
};
Custom Upload Service
import type {
UploadFilesService,
UploadTrackerFileToUpload,
} from "@ecp.eth/react-editor/types";
declare const uploadToYourService: (
file: UploadTrackerFileToUpload,
) => Promise<string>;
const customUploads: UploadFilesService = {
allowedMimeTypes: ["image/png", "image/jpeg"],
maxFileSize: 5 * 1024 * 1024, // 5MB
uploadFile: async (file, callbacks) => {
// Implement your own upload logic
const response = await uploadToYourService(file);
callbacks?.onSuccess?.(
{
id: file.id,
name: file.name,
url: response,
mimeType: file.mimeType,
},
response,
);
return response;
},
uploadFiles: async (files, callbacks) => {
// Implement batch upload logic
return Promise.all(
files.map((file) => customUploads.uploadFile(file, callbacks)),
);
},
};
Complete Example
Here's how you might combine both the Component Library and Editor in a single application:
import React, { useState } from "react";
import { CommentsEmbed } from "@ecp.eth/sdk/embed";
import { Editor } from "@ecp.eth/react-editor/editor";
import {
useIndexerSuggestions,
usePinataUploadFiles,
} from "@ecp.eth/react-editor/hooks";
function CommentsSection() {
const [showEditor, setShowEditor] = useState(false);
const suggestions = useIndexerSuggestions();
const uploads = usePinataUploadFiles({
pinataGatewayUrl: "https://api.pinata.cloud",
generateUploadUrl: async (filename) => {
return `https://api.pinata.cloud/files/${filename}`;
},
});
return (
<div>
<h2>Comments</h2>
{/* Display existing comments */}
<CommentsEmbed
uri={new URL(window.location.pathname, "https://demo.ethcomments.xyz")}
theme={{ mode: "light" }}
/>
{/* Add new comment button */}
<button onClick={() => setShowEditor(true)}>Add Comment</button>
{/* Comment editor */}
{showEditor && (
<div>
<Editor
placeholder="Write your comment..."
suggestions={suggestions}
uploads={uploads}
onBlur={() => setShowEditor(false)}
/>
</div>
)}
</div>
);
}
Next Steps
- Check out how to retrieve comments and replies
- Learn about contract interactions for direct blockchain integration
- Explore the TypeScript SDK for programmatic access