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.
- Rust Backend: Includes a simple Rust canister for posts and likes.
- Authentication: Connect with Internet Identity.
- Form Handling: Creating new posts with loading states.
- Automatic Refetching: Lists update automatically after mutations.
Open in StackBlitzEdit and run in your browser
View on GitHubBrowse the source code
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 "../canisters/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:
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(),})Optimistic Updates
Section titled “Optimistic Updates”The demo uses React’s useOptimistic rather than writing into the query cache,
so the optimistic state unwinds by itself when the transition settles:
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))