All-in-One Demo
A simplified “Twitter-like” dApp that demonstrates the full power of IC Reactor, including optimistic UI updates, backend integration, and authentication.
Features Demonstrated
Section titled “Features Demonstrated”- Optimistic Updates: UI updates instantly when you “Like” a post, rolling back on error.
- Motoko Backend: Includes a simple Motoko canister for posts and likes.
- Generated Reactor: The Vite plugin generates the backend’s
DisplayReactorand hooks intosrc/declarations/backend/from its Candid. - Authentication: Connect with Internet Identity.
- Form Handling: Creating new posts with loading states.
- Automatic Refetching: Lists update automatically after mutations.
Live Preview
Section titled “Live Preview”Key Code
Section titled “Key Code”Reusable Query and Mutation Objects
Section titled “Reusable Query and Mutation Objects”import { createMutation, createQuery } from "@ic-reactor/react"import { backendReactor } from "../declarations/backend"
export const getLikes = createQuery(backendReactor, { functionName: "get_likes", refetchInterval: 3000,})
export const likeHeart = createMutation(backendReactor, { functionName: "like",})Three Mutation Callbacks
Section titled “Three Mutation Callbacks”onCanisterError fires only for canister Result { Err } variants and receives
two arguments — the CanisterError and the mutation variables. onError
fires for every error and is the one that receives the onMutate context.
The demo’s useHeart(addLog) hook, in src/lib/useHeart.ts, returns
{ likesCount, isLiked, isLikesLoading, handleHeartClick, savedUpdates }. This
excerpt and the next each show one part of it, as a hook of its own:
// An excerpt of useHeart (src/lib/useHeart.ts): its mutation callbacksimport type { FrontendLog } from "./types"import { getLikes, likeHeart } from "./factories"
export function useLikeMutation( addLog: (type: FrontendLog["type"], message: string) => void) { const { mutateAsync: likeMutate } = likeHeart.useMutation({ // Canister returned { Ok } onSuccess: () => { addLog("success", "Like confirmed by canister") }, // Canister returned { Err } — business logic errors only onCanisterError: (error, variables) => { addLog("error", `Canister rejected like: ${error.message}`) }, // Every error, including network and agent failures onError: (error, variables, context) => { console.error("Like mutation error:", error) }, onSettled: () => getLikes.invalidate(), })
return { likeMutate }}Optimistic Updates
Section titled “Optimistic Updates”The demo’s useHeart uses React’s useOptimistic rather than writing into the
query cache, so the optimistic state unwinds by itself when the transition
settles. Here is that part on its own (the demo keeps it inside useHeart, next
to the two mutations and the debounce):
// An excerpt of useHeart (src/lib/useHeart.ts): its optimistic stateimport { useOptimistic } from "react"import { getLikes } from "./factories"
export function useOptimisticLikes() { const { data: likes = [] } = getLikes.useQuery()
const [optimisticLikes, addOptimisticLike] = useOptimistic( likes, (state, newLike: { type: "add" | "remove"; principal: string }) => newLike.type === "add" ? [...state, newLike.principal] : state.filter((p) => p !== newLike.principal) )
return { likesCount: optimisticLikes.length, addOptimisticLike }}