Getting started with React
See a live example (opens in a new tab)
Add the core libraries
yarn add @mod-protocol/react @mod-protocol/react-editor @mod-protocol/miniapp-registry
Add the default UI library, or use your own. The default uses shadcn (opens in a new tab) which uses Tailwind (opens in a new tab) + Radix UI (opens in a new tab) under the hood We've made these packages modular to reduce unnecessary packages, facilitate tree shaking, and allow for flexibility to integrate natively with your UI.
yarn add @mod-protocol/react-ui-shadcn
Mod Editor for Farcaster Cast creation
import * as React from "react";
// Core
import {
Channel,
getFarcasterChannels,
getFarcasterMentions,
} from "@mod-protocol/farcaster";
import { CreationMiniApp } from "@mod-protocol/react";
import { useEditor, EditorContent } from "@mod-protocol/react-editor";
import { creationMiniApps } from "@mod-protocol/miniapp-registry";
import {
Embed,
ModManifest,
fetchUrlMetadata,
handleAddEmbed,
handleOpenFile,
handleSetInput,
} from "@mod-protocol/core";
// UI implementation
import { createRenderMentionsSuggestionConfig } from "@mod-protocol/react-ui-shadcn/dist/lib/mentions";
import { CreationMiniAppsSearch } from "@mod-protocol/react-ui-shadcn/dist/components/creation-miniapps-search";
import { CastLengthUIIndicator } from "@mod-protocol/react-ui-shadcn/dist/components/cast-length-ui-indicator";
import { ChannelPicker } from "@mod-protocol/react-ui-shadcn/dist/components/channel-picker";
import { EmbedsEditor } from "@mod-protocol/react-ui-shadcn/dist/lib/embeds";
import { Button } from "@mod-protocol/react-ui-shadcn/dist/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@mod-protocol/react-ui-shadcn/dist/components/ui/popover";
import { renderers } from "@mod-protocol/react-ui-shadcn/dist/renderers";
// Optionally replace with your API_URL here if you want to self host by running your own instance of https://github.com/mod-protocol/mod/tree/main/examples/api
const API_URL = "https://api.modprotocol.org";
const getResults = getFarcasterMentions(API_URL);
const getChannels = getFarcasterChannels(API_URL);
const getUrlMetadata = fetchUrlMetadata(API_URL);
const onError = (err) => window.alert(err.message);
const onSubmit = async ({
text,
embeds,
channel,
}: {
text: string;
embeds: Embed[];
channel: Channel;
}) => {
window.alert(
`This is a demo, and doesn't do anything.\n\nCast text:\n${text}\nEmbeds:\n${embeds
.map((embed) => (embed as any).url)
.join(", ")}\nChannel:\n${channel.name}`
);
return true;
};
export default function EditorExample() {
const {
editor,
getText,
getEmbeds,
setEmbeds,
setText,
setChannel,
getChannel,
addEmbed,
handleSubmit,
} = useEditor({
fetchUrlMetadata: getUrlMetadata,
onError,
onSubmit,
linkClassName: "text-blue-600",
renderMentionsSuggestionConfig: createRenderMentionsSuggestionConfig({
getResults: getResults,
}),
});
const [currentMiniapp, setCurrentMiniapp] =
React.useState<ModManifest | null>(null);
return (
<form onSubmit={handleSubmit}>
<div className="p-2 border-slate-200 rounded-md border">
<EditorContent
editor={editor}
autoFocus
className="w-full h-full min-h-[200px]"
/>
<EmbedsEditor embeds={getEmbeds()} setEmbeds={setEmbeds} />
</div>
<div className="flex flex-row pt-2 gap-1">
<ChannelPicker
getChannels={getChannels}
onSelect={setChannel}
value={getChannel()}
/>
<Popover
open={!!currentMiniapp}
onOpenChange={(op: boolean) => {
if (!op) setCurrentMiniapp(null);
}}
>
<PopoverTrigger></PopoverTrigger>
<CreationMiniAppsSearch
miniapps={creationMiniApps}
onSelect={setCurrentMiniapp}
/>
<PopoverContent className="w-[400px] ml-2" align="start">
<div className="space-y-4">
<h4 className="font-medium leading-none">
{currentMiniapp?.name}
</h4>
<hr />
<CreationMiniApp
input={getText()}
embeds={getEmbeds()}
api={API_URL}
variant="creation"
manifest={currentMiniapp}
renderers={renderers}
onOpenFileAction={handleOpenFile}
onExitAction={() => setCurrentMiniapp(null)}
onSetInputAction={handleSetInput(setText)}
onAddEmbedAction={handleAddEmbed(addEmbed)}
/>
</div>
</PopoverContent>
</Popover>
<CastLengthUIIndicator getText={getText} />
<div className="grow"></div>
<Button type="submit">Cast</Button>
</div>
</form>
);
}
Embed rendering Mini-apps
"use client";
import { ContextType, Embed } from "@mod-protocol/core";
import {
contentMiniApps,
defaultContentMiniApp,
} from "@mod-protocol/miniapp-registry";
import { RenderEmbed } from "@mod-protocol/react";
import { renderers } from "@mod-protocol/react-ui-shadcn/dist/renderers";
import {
sendTransaction,
switchNetwork,
waitForTransaction,
} from "@wagmi/core";
import { useMemo } from "react";
import { useAccount } from "wagmi";
export function Embeds(props: { embeds: Array<Embed> }) {
const { address } = useAccount();
// Handle send transaction e.g. on click of a mint button
// This example uses wagmi
const onSendEthTransactionAction = useMemo(
() =>
async ({ data, chainId }, { onConfirmed, onError, onSubmitted }) => {
try {
const parsedChainId = parseInt(chainId);
// Switch chains if the user is not on the right one
await switchNetwork({ chainId: parsedChainId });
// Send the transaction
const { hash } = await sendTransaction({
...data,
chainId: parsedChainId,
});
onSubmitted(hash);
// Wait for the transaction to be confirmed
const { status } = await waitForTransaction({
hash,
chainId: parsedChainId,
});
onConfirmed(hash, status === "success");
} catch (e) {
onError(e);
}
},
[]
);
const context = useMemo<Omit<ContextType, "embed">>(() => {
return {
/* Required context */
api: process.env.NEXT_PUBLIC_API_URL,
/* Optional context */
user: {
id: "3", // Current user's FID
wallet: {
address: "0x1234...", // Current user's wallet address
}
},
};
}, [address]);
return (
<div>
{props.embeds.map((embed, i) => (
<RenderEmbed
embed={embed}
{...context}
key={i}
renderers={renderers}
defaultContentMiniApp={defaultContentMiniApp}
contentMiniApps={contentMiniApps}
resolvers={{
onSendEthTransactionAction,
}}
/>
))}
</div>
);
}
Integrating Creation Mini-apps with your own editor
You can integrate Creation Mini-apps with an existing editor, just pass the text and embeds from your editor into the CreationMiniApp
component.