@coin-communities/sdk/react-query ships hook bindings around every operation in the SDK Reference, an optimistic-post reconciliation hook, plus a queryKeys factory for cache invalidation and prefetching. The entry also re-exports configureApi and the api namespace, so it's the only import path most React apps need.

@tanstack/react-query (v5+) is a peer dependency.


Setup

pnpm add @coin-communities/sdk @tanstack/react-query

Wrap your app in a QueryClientProvider and configure the SDK once at boot:

import { configureApi } from '@coin-communities/sdk/react-query';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 
configureApi({
  baseUrl: 'https://api.coin-communities.xyz',
  auth: () => getToken(),
});
 
const queryClient = new QueryClient();
 
export function App({ children }: { children: React.ReactNode }) {
  return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
}

Conventions

  • Read operations (GET) are exposed as useXxx() hooks returning UseQueryResult<TData, TError>.
  • Write operations (POST / PUT / DELETE) are exposed as useXxx() hooks returning UseMutationResult<TData, TError, TVars>.
  • Hooks accept the standard React Query options object as their last argument (onSuccess, onError, select, enabled, …) — query/mutation keys and functions are managed for you.
  • Mutations that target a single resource (e.g. useFollowUser) take the resource id as the mutation variable rather than an options object.
  • Hooks scoped to a community (useMessages, usePostMessage, …) take tokenAddress as the first positional argument.

Hook catalog

Diagnostics

HookWrapsKind
useHealth(opts?)api.health()query
usePing(opts?)api.ping()query

Session

HookWrapsKind
useRefreshToken(opts?)api.refreshToken()mutation (RefreshTokenRequest)

Communities

HookWrapsKind
useCommunity(tokenAddress, opts?)api.getCommunity()query
useMessages(tokenAddress, query?, opts?)api.getMessages()query
useMessagesPublic(tokenAddress, opts?)api.getMessagesPublic()query
usePostMessage(tokenAddress, opts?)api.postMessage()mutation (PostMessageRequest)
useOptimisticCommunityPosts(tokenAddress, opts)realtime + message cache reconciliationlocal optimistic state
useLikeMessage(tokenAddress, opts?)api.likeMessage()mutation (messageId: string)
useUnlikeMessage(tokenAddress, opts?)api.unlikeMessage()mutation (messageId: string)
useLikeCount(tokenAddress, messageId, opts?)api.getLikeCount()query
useReportMessage(tokenAddress, opts?)api.reportMessage()mutation (ReportMessageRequest & { messageId })

Community media

HookWrapsKind
useUploadCommunityMedia(opts?)api.uploadCommunityMedia()mutation (UploadMediaRequest)
useMediaProviders(opts?)api.getMediaProviders()query

Chains & Feed

HookWrapsKind
useSupportedChains(opts?)api.getSupportedChains()query
useTopCommunities(query?, opts?)api.getTopCommunities()query
useFeed(opts?)api.getFeed()query
useFeedPublic(opts?)api.getFeedPublic()query

Users

HookWrapsKind
useUser(opts?)api.userMe()query

Wallets

HookWrapsKind
useWallets(opts?)api.getWallets()query
useWalletChallenge(opts?)api.walletChallenge()mutation (WalletChallengeRequest)
useLinkWallet(opts?)api.linkWallet()mutation (LinkWalletRequest)

Twitter

HookWrapsKind
useTwitterAuthUrl(query, opts?)api.twitterAuthUrl()query
useTwitterCallback(opts?)api.twitterCallback()mutation (TwitterCallbackRequest)
useTwitterChallengeExchange(opts?)api.twitterChallengeExchange()mutation (TwitterChallengeExchangeRequest)

User Social Graph

HookWrapsKind
useUserProfile(userId, opts?)api.getUserProfile()query
useUserFollowers(userId, params?, opts?)api.getFollowers()query
useUserFollowing(userId, params?, opts?)api.getFollowing()query
useFollowUser(opts?)api.followUser()mutation (userId: string) — auto-invalidates profile + follow lists
useUnfollowUser(opts?)api.unfollowUser()mutation (userId: string) — auto-invalidates profile + follow lists

Message Replies

HookWrapsKind
useMessageReplies(tokenAddress, messageId, params?, opts?)api.getReplies()query
usePostReply(tokenAddress, messageId, opts?)api.postReply()mutation (PostMessageRequest) — auto-invalidates replies list

Community Members

HookWrapsKind
useCommunityMembers(tokenAddress, params?, opts?)api.getCommunityMembers()query

Realtime

useOptimisticCommunityPosts is the recommended companion to usePostMessage. It creates pending Message objects, subscribes to realtime events, reconciles clean or moderated message_update events, invalidates the message cache on resolution/gaps, and exposes mergePendingPosts(fetched) for rendering.

import { useOptimisticCommunityPosts } from '@coin-communities/sdk/react-query';
 
const optimistic = useOptimisticCommunityPosts(tokenAddress, {
  auth: {
    getTicket: async () => {
      const { data } = await api.getWsTicket({ path: { token_address: tokenAddress } });
      if (!data?.ticket) throw new Error('WebSocket ticket unavailable');
      return data.ticket;
    },
  },
  author: { userId: me.id, username: me.username },
  onReject: () => toast.error('Your post was removed by moderation.'),
  onModeration: () => toast.error('Your post was removed by moderation.'),
});
 
const feed = optimistic.mergePendingPosts(messages.data?.messages ?? []);

Use the lower-level realtime hooks from @coin-communities/sdk/react when you want to handle raw message_update, like_update, moderation_update, and onGap events yourself.


queryKeys factory

queryKeys is a stable, typed object literal that exposes the cache key for every query the hooks use. Reach for it when you want to invalidate, prefetch, or read cache entries directly.

import { queryKeys } from '@coin-communities/sdk/react-query';
 
queryKeys.health;                                                    // ['health']
queryKeys.ping;                                                      // ['ping']
queryKeys.feed;                                                      // ['feed']
queryKeys.feedPublic;                                                // ['feed', 'public']
queryKeys.user.me;                                                   // ['user', 'me']
queryKeys.user.wallets;                                              // ['user', 'wallets']
queryKeys.user.profile(userId);                                      // ['user', userId, 'profile']
queryKeys.user.followers(userId, params?);                           // ['user', userId, 'followers', params]
queryKeys.user.following(userId, params?);                           // ['user', userId, 'following', params]
queryKeys.community.all;                                             // ['community']
queryKeys.community.top(query?);                                     // ['community', 'top', query]
queryKeys.community.detail(tokenAddress);                            // ['community', tokenAddress]
queryKeys.community.messages(tokenAddress, q);                       // ['community', tokenAddress, 'messages', q]
queryKeys.community.messagesPublic(tokenAddress);                    // ['community', tokenAddress, 'messages', 'public']
queryKeys.community.likeCount(tokenAddress, mid);                    // ['community', tokenAddress, 'messages', mid, 'likes']
queryKeys.community.replies(tokenAddress, messageId, params?);       // ['community', tokenAddress, 'messages', messageId, 'replies', params]
queryKeys.community.members(tokenAddress, params?);                  // ['community', tokenAddress, 'members', params]
queryKeys.media.providers;                                           // ['media', 'providers']
queryKeys.chains.supported;                                          // ['chains', 'supported']
queryKeys.twitter.authUrl(query);                                    // ['twitter', 'auth-url', query]

The *.all keys are deliberately broad — pass them to queryClient.invalidateQueries({ queryKey }) to refresh every variant of a resource at once.


End-to-end example

A list-and-post flow that invalidates the messages cache after a successful mutation:

import {
  queryKeys,
  useMessages,
  usePostMessage,
} from '@coin-communities/sdk/react-query';
import { useQueryClient } from '@tanstack/react-query';
 
export function CommunityFeed({ tokenAddress }: { tokenAddress: string }) {
  const qc = useQueryClient();
  const messages = useMessages(tokenAddress);
  const post = usePostMessage(tokenAddress, {
    onSuccess: () =>
      qc.invalidateQueries({ queryKey: queryKeys.community.messages(tokenAddress) }),
  });
 
  if (messages.isLoading) return <p>Loading…</p>;
  if (messages.isError) return <p>Failed to load messages.</p>;
 
  return (
    <>
      <ul>{messages.data?.messages.map((m) => <li key={m.id}>{m.content}</li>)}</ul>
      <button
        type="button"
        disabled={post.isPending}
        onClick={() =>
          post.mutate({ content: 'gm', chainId: 'solana', walletAddress: wallet })
        }
      >
        Post
      </button>
    </>
  );
}