React hooks for the Bitsocial protocol. Build decentralized, serverless social apps with React using a familiar hooks API — fetch feeds, comments, author profiles, manage accounts, publish content, and more, all without a central server.
This package is currently consumed directly from bitsocialnet/bitsocial-react-hooks and is used by 5chan and other Bitsocial clients.
Note: This repo is a temporary Bitsocial fork of plebbit/plebbit-react-hooks for AI-aided development. Bug fixes, new features, and improvements made here will be merged upstream when the original maintainer is ready. The codebase still uses legacy naming (
plebbit,plebbit-js, etc.) pending upstream rebranding of the protocol layer.
yarn add https://github.com/bitsocialnet/bitsocial-react-hooks.git#<commit-hash>Use a pinned commit hash (or tag) so installs are reproducible.
- Installation
- Documentation Links
- API Reference
- Recipes
- Getting started
- Get the active account, if none exist in browser database, a default account is generated
- Create accounts and change active account
- Get a post
- Get a comment
- Get author avatar
- Get author profile page
- Get a community
- Create a post or comment using callbacks
- Create a post or comment using hooks
- Create a post or comment anonymously (without account.signer or account.author)
- Create a vote
- Create a comment edit
- Create a comment moderation
- Delete a comment
- Subscribe to a community
- Get feed
- Get mod queue (pending approval)
- Approve a pending approval comment
- Edit an account
- Delete account
- Get your own comments and votes
- Determine if a comment is your own
- Get account notifications
- Block an address (author, community or multisub)
- Block a cid (hide a comment)
- (Desktop only) Create a community
- (Desktop only) List the communities you created
- (Desktop only) Edit your community settings
- Export and import account
- View the status of a comment edit
- View the status of a specific comment edit property
- List all comment and community edits the account has performed
- Get replies to a post (nested or flat)
- Get a shortCid or shortAddress (plebbit-js)
- Get a shortCid or shortAddress (hooks)
- useBufferedFeeds with concurrency
- Hooks API
- Getting started
- Install, testing and building: https://github.com/bitsocialnet/bitsocial-react-hooks/blob/master/docs/testing.md
- Mock content (for UI development): https://github.com/bitsocialnet/bitsocial-react-hooks/blob/master/docs/mock-content.md
- Algorithms: https://github.com/bitsocialnet/bitsocial-react-hooks/blob/master/docs/algorithms.md
- Schema (Types, IndexedDb and state management): https://github.com/bitsocialnet/bitsocial-react-hooks/blob/master/docs/schema.md
- Types: https://github.com/bitsocialnet/bitsocial-react-hooks/blob/master/src/types.ts
useAccount(): Account | undefined
useAccountComment({commentIndex: string}): Comment // get a pending published comment by its index
useAccountComments({filter: AccountPublicationsFilter}): {accountComments: Comment[]} // export or display list of own comments
useAccountVotes({filter: AccountPublicationsFilter}): {accountVotes: Vote[]} // export or display list of own votes
useAccountVote({commentCid: string}): Vote // know if you already voted on some comment
useAccountEdits({filer: AccountPublicationsFilter}): {accountEdits: AccountEdit[]}
useAccountCommunities(): {accountCommunities: {[communityAddress: string]: AccountCommunity}, onlyIfCached?: boolean}
useAccounts(): Account[]
useNotifications(): {notifications: Notification[], markAsRead: Function}
useComment({commentCid: string, onlyIfCached?: boolean}): Comment
useReplies({comment: Comment, sortType?: string, flat?: boolean, repliesPerPage?: number, filter?: CommentsFilter, accountComments?: {newerThan: number, append?: boolean}}): {replies: Comment[], hasMore: boolean, loadMore: function, reset: function, updatedReplies: Comment[], bufferedReplies: Comment[]}
useComments({commentCids: string[], onlyIfCached?: boolean}): {comments: Comment[]}
useEditedComment({comment: Comment}): {editedComment: Comment | undefined}
useValidateComment({comment: Comment, validateReplies?: boolean}): {valid: boolean}
useCommunity({communityAddress: string, onlyIfCached?: boolean}): Community
useCommunities({communityAddresses: string[], onlyIfCached?: boolean}): {communities: Communities[]}
useCommunityStats({communityAddress: string, onlyIfCached?: boolean}): CommunityStats
useResolvedCommunityAddress({communityAddress: string, cache: boolean}): {resolvedAddress: string | undefined} // use {cache: false} when checking the user's own community address
useAuthor({authorAddress: string, commentCid: string}): {author: Author | undefined}
useAuthorAddress({comment: Comment}): {authorAddress: string | undefined, shortAuthorAddress: string | undefined, authorAddressChanged: boolean}
useAuthorComments({authorAddress: string, commentCid: string, filter?: CommentsFilter}): {authorComments: Comment[], hasMore: boolean, loadMore: Promise<void>}
useResolvedAuthorAddress({author?: Author, cache?: boolean}): {resolvedAddress: string | undefined} // supports .eth/.bso aliases and .sol; use {cache: false} when checking the user's own author address
useAuthorAvatar({author?: Author}): {imageUrl: string | undefined}
setAuthorAvatarsWhitelistedTokenAddresses(tokenAddresses: string[])
useFeed({communityAddresses: string[], sortType?: string, postsPerPage?: number, filter?: CommentsFilter, newerThan?: number, accountComments?: {newerThan: number, append?: boolean}, modQueue: ['pendingApproval']}): {feed: Comment[], loadMore: function, hasMore: boolean, reset: function, updatedFeed: Comment[], bufferedFeed: Comment[], communityAddressesWithNewerPosts: string[]}
useBufferedFeeds({feedsOptions: UseFeedOptions[]}) // preload or buffer feeds in the background, so they load faster when you call `useFeed`
useFeed().reset() clears the current feed and refreshes the latest community snapshots before rebuilding it.
useSubscribe({communityAddress: string}): {subscribed: boolean | undefined, subscribe: Function, unsubscribe: Function}
useBlock({address?: string, cid?: string}): {blocked: boolean | undefined, block: Function, unblock: Function}
usePublishComment(options: UsePublishCommentOptions): {index: number, abandonPublish: () => Promise<void>, ...UsePublishCommentResult}
usePublishVote(options: UsePublishVoteOptions): UsePublishVoteResult
usePublishCommentEdit(options: UsePublishCommentEditOptions): UsePublishCommentEditResult
usePublishCommentModeration(options: UsePublishCommentModerationOptions): UsePublishCommentModerationResult
usePublishCommunityEdit(options: UsePublishCommunityEditOptions): UsePublishCommunityEditResult
useCreateCommunity(options: CreateCommunityOptions): {createdCommunity: Community | undefined, createCommunity: Function}
useClientsStates({comment?: Comment, community?: Community}): {states, peers}
useCommunitiesStates({communityAddresses: string[]}): {states, peers}
usePlebbitRpcSettings(): {plebbitRpcSettings: {plebbitOptions, challenges}, setPlebbitRpcSettings: Function}
createAccount(account: Account)
deleteAccount(accountName: string)
setAccount(account: Account)
setActiveAccount(accountName: string)
setAccountsOrder(accountNames: string[])
importAccount(serializedAccount: string)
exportAccount(accountName: string): string // don't allow undefined to prevent catastrophic bugs
deleteCommunity(communityAddress: string, accountName?: string)
deleteComment(commentCidOrAccountCommentIndex: string | number, accountName?: string): Promise<void>
setPlebbitJs(PlebbitJs) // set which plebbit-js version to use, e.g. to mock content for frontend dev or to use the node version in Electron
deleteDatabases() // delete all databases, including all caches and accounts data
deleteCaches() // delete the cached comments, cached communities and cached pages only, no accounts data
import { useComment, useAccount } from "@bitsocialnet/bitsocial-react-hooks";
const account = useAccount();
const comment = useComment({ commentCid });const account = useAccount();import {
useAccount,
useAccounts,
createAccount,
setActiveAccount,
} from "@bitsocialnet/bitsocial-react-hooks";
const account = useAccount();
const { accounts } = useAccounts();
// on first render
console.log(accounts.length); // 1
console.log(account.name); // 'Account 1'
await createAccount(); // create 'Account 2'
await createAccount(); // create 'Account 3'
await setActiveAccount("Account 3");
// on render after updates
console.log(accounts.length); // 3
console.log(account.name); // 'Account 3'
// you are now publishing from 'Account 3' because it is the active one
const { publishComment } = usePublishComment(publishCommentOptions);
await publishComment();const post = useComment({ commentCid });
// post.author.address should not be used directly, it needs to be verified asynchronously using useAuthorAddress
const { authorAddress, shortAuthorAddress } = useAuthorAddress({ comment: post });
// exception: when linking to an author profile page, /u/${comment.author.address}/c/${comment.cid} should be used, not useAuthorAddress({comment}).authorAddress
// use many times in a page without affecting performance
const post = useComment({ commentCid, onlyIfCached: true });
// post.replies are not validated, to show replies
const { replies, hasMore, loadMore } = useReplies({ comment: post });
// to show a preloaded reply without rerenders, validate manually
const { valid } = useValidateComment({ comment: post.replies.pages.best.comments[0] });
if (valid === false) {
// don't show this reply, it's malicious
}
// won't cause any rerenders if trueconst comment = useComment({ commentCid });
const { comments } = useComments({ commentCids: [commentCid1, commentCid2, commentCid3] });
// content
console.log(comment.content || comment.link || comment.title);
// comment.author.address should not be used directly, it needs to be verified asynchronously using useAuthorAddress
const { authorAddress, shortAuthorAddress } = useAuthorAddress({ comment });
// exception: when linking to an author profile page, /u/${comment.author.address}/c/${comment.cid} should be used, not useAuthorAddress({comment}).authorAddress
// use without affecting performance
const { comments } = useComments({ commentCids, onlyIfCached: true });const comment = useComment({ commentCid });
// get the nft avatar image url of the comment author
const { imageUrl, state, error, chainProvider, metadataUrl } = useAuthorAvatar({
author: comment.author,
});
// result
if (state === "succeeded") {
console.log("Succeeded getting avatar image URL", imageUrl);
}
if (state === "failed") {
console.log("Failed getting avatar image URL", error.message);
}
// pending
if (state === "fetching-owner") {
console.log("Fetching NFT owner address from chain provider", chainProvider.urls);
}
if (state === "fetching-uri") {
console.log("Fetching NFT URI from chain provider URL", chainProvider.urls);
}
if (state === "fetching-metadata") {
console.log("Fetching NFT URI from", metadataUrl);
}// NOTE: you must have a comment cid from the author to load his profile page
// e.g. the page url would be /#/u/<authorAddress>/c/<commentCid>
const authorResult = useAuthor({ commentCid, authorAddress });
const { imageUrl } = useAuthorAvatar({ author: authorResult.author });
const { authorComments, lastCommentCid, hasMore, loadMore } = useAuthorComments({
commentCid,
authorAddress,
});
// result
if (authorResult.state === "succeeded") {
console.log("Succeeded getting author", authorResult.author);
}
if (state === "failed") {
console.log("Failed getting author", authorResult.error.message);
}
// listing the author comments with infinite scroll
import { Virtuoso } from "react-virtuoso";
<Virtuoso
data={authorComments}
itemContent={(index, comment) => <Comment index={index} comment={comment} />}
useWindowScroll={true}
components={{ Footer: hasMore ? () => <Loading /> : undefined }}
endReached={loadMore}
increaseViewportBy={{ bottom: 600, top: 600 }}
/>;
// it is recommended to always redirect the user to the last known comment cid
// in case they want to share the url with someone, the author's comments
// will load faster when using the last comment cid
import { useParams } from "react-router-dom";
const params = useParams();
useEffect(() => {
if (lastCommentCid && params.comentCid !== lastCommentCid) {
history.push(`/u/${params.authorAddress}/c/${lastCommentCid}`);
}
}, [lastCommentCid]);
// search an author's comments
const createSearchFilter = (searchTerm) => ({
filter: (comment) => comment.title?.includes(searchTerm) || comment.content?.includes(searchTerm),
key: `includes-${searchTerm}`, // required key to cache the filter
});
const filter = createSearchFilter("bitcoin");
const { authorComments, lastCommentCid, hasMore, loadMore } = useAuthorComments({
commentCid,
authorAddress,
filter,
});const community = useCommunity({ communityAddress });
const communityStats = useCommunityStats({ communityAddress });
const { communities } = useCommunities({
communityAddresses: [communityAddress, communityAddress2, communityAddress3],
});
// use without affecting performance
const { communities } = useCommunities({
communityAddresses: [communityAddress, communityAddress2, communityAddress3],
onlyIfCached: true,
});
// community.posts are not validated, to show posts
const { feed, hasMore, loadMore } = useFeed({ communityAddresses: [communityAddress] });
// to show a preloaded post without rerenders, validate manually
const { valid } = useValidateComment({ comment: community.posts.pages.topAll.comments[0] });
if (valid === false) {
// don't show this post, it's malicious
}
// won't cause any rerenders if trueconst onChallenge = async (challenges: Challenge[], comment: Comment) => {
let challengeAnswers: string[]
try {
// ask the user to complete the challenges in a modal window
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (e) {
// if he declines, throw error and don't get a challenge answer
}
if (challengeAnswers) {
// if user declines, publishChallengeAnswers is not called, retry loop stops
await comment.publishChallengeAnswers(challengeAnswers)
}
}
const onChallengeVerification = (challengeVerification, comment) => {
// if the challengeVerification fails, a new challenge request will be sent automatically
// to break the loop, the user must decline to send a challenge answer
// if the community owner sends more than 1 challenge for the same challenge request, subsequents will be ignored
if (challengeVerification.challengeSuccess === true) {
console.log('challenge success', {publishedCid: challengeVerification.publication.cid})
}
else if (challengeVerification.challengeSuccess === false) {
console.error('challenge failed', {reason: challengeVerification.reason, errors: challengeVerification.errors})
}
}
const onError = (error, comment) => console.error(error)
const publishCommentOptions = {
content: 'hello',
title: 'hello',
communityAddress: '12D3KooW...',
onChallenge,
onChallengeVerification,
onError
}
const {index, state, publishComment, abandonPublish} = usePublishComment(publishCommentOptions)
// create post
await publishComment()
// pending comment index
console.log(index)
// pending comment state
console.log(state)
// after publishComment is called, the account comment index gets defined
// it is recommended to immediately redirect the user to a page displaying
// the user's comment with a "pending" label
if (index !== undefined) {
history.push(`/profile/c/${index}`)
// on the "pending" comment page, you can get the pending comment by doing
// const accountComment = useAccountComment({commentIndex: index})
// after accountComment.cid gets defined, it means the comment was published successfully
// it is recommended to immediately redirect to `/p/${accountComment.communityAddress}/c/${useAccountComment.cid}`
}
// if the user closes the challenge modal and wants to cancel publishing:
await abandonPublish()
// the pending local account comment is removed from accountComments
// this works even if called immediately from onChallenge before publishComment() resolves
// reply to a post or comment
const publishReplyOptions = {
content: 'hello',
parentCid: 'Qm...', // the cid of the comment to reply to
communityAddress: '12D3KooW...',
onChallenge,
onChallengeVerification,
onError
}
const {publishComment} = usePublishComment(publishReplyOptions)
await publishComment()
// when displaying replies, it is recommended to include the user's pending replies
// https://github.com/bitsocialnet/bitsocial-react-hooks/#get-replies-to-a-post-nested (nested)
// https://github.com/bitsocialnet/bitsocial-react-hooks/#get-replies-to-a-post-flattened-not-nested (not nested)const publishCommentOptions = {
content: "hello",
title: "hello",
communityAddress: "12D3KooW...",
};
const {
index,
state,
publishComment,
challenge,
challengeVerification,
publishChallengeAnswers,
abandonPublish,
error,
} = usePublishComment(publishCommentOptions);
if (challenge) {
// display challenges to user and call publishChallengeAnswers(challengeAnswers)
}
if (challengeVerification) {
// display challengeVerification.challengeSuccess to user
// redirect to challengeVerification.publication.cid
}
if (error) {
// display error to user
}
// if the user closes your challenge modal:
if (challenge && challengeModalClosedByUser) {
await abandonPublish();
}
// after publishComment is called, the account comment index gets defined
// it is recommended to immediately redirect the user to a page displaying
// the user's comment with a "pending" label
if (index !== undefined) {
history.push(`/profile/c/${index}`);
// on the "pending" comment page, you can get the pending comment by doing
// const accountComment = useAccountComment({commentIndex: index})
// after accountComment.cid gets defined, it means the comment was published successfully
// it is recommended to immediately redirect to `/p/${accountComment.communityAddress}/c/${useAccountComment.cid}`
}
// create post
await publishComment();const account = useAccount();
const signer = await account.plebbit.createSigner();
const publishCommentOptions = {
content: "hello",
title: "hello",
communityAddress: "12D3KooW...",
// use a newly generated author address (optional)
signer,
// use a different display name (optional)
author: {
displayName: "Esteban",
address: signer.address,
},
};
const { publishComment } = usePublishComment(publishCommentOptions);
await publishComment();const commentCid = "QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui";
const publishVoteOptions = {
commentCid,
vote: 1,
communityAddress: "news.eth",
onChallenge,
onChallengeVerification,
onError,
};
const { state, error, publishVote } = usePublishVote(publishVoteOptions);
await publishVote();
console.log(state);
console.log(error);
// display the user's vote
const { vote } = useAccountVote({ commentCid });
if (vote === 1) console.log("user voted 1");
if (vote === -1) console.log("user voted -1");
if (vote === 0) console.log("user voted 0");
if (vote === undefined) console.log(`user didn't vote yet`);const publishCommentEditOptions = {
commentCid: "QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui",
content: "edited content",
communityAddress: "news.eth",
onChallenge,
onChallengeVerification,
onError,
};
const { state, error, publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
await publishCommentEdit();
console.log(state);
console.log(error);
// view the status of a comment edit instantly
let comment = useComment({ commentCid: publishCommentEditOptions.commentCid });
const { state: editedCommentState, editedComment } = useEditedComment({ comment });
// if the comment has a succeeded, failed or pending edit, use the edited comment
if (editedComment) {
comment = editedComment;
}
let editLabel;
if (editedCommentState === "succeeded") {
editLabel = { text: "EDITED", color: "green" };
}
if (editedCommentState === "pending") {
editLabel = { text: "PENDING EDIT", color: "orange" };
}
if (editedCommentState === "failed") {
editLabel = { text: "FAILED EDIT", color: "red" };
}const publishCommentModerationOptions = {
commentCid: "QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui",
communityAddress: "news.eth",
commentModeration: { locked: true },
onChallenge,
onChallengeVerification,
onError,
};
const { state, error, publishCommentModeration } = usePublishCommentModeration(
publishCommentModerationOptions,
);
await publishCommentModeration();
console.log(state);
console.log(error);
// view the status of a comment moderation instantly
let comment = useComment({ commentCid: publishCommentModerationOptions.commentCid });
const { state: editedCommentState, editedComment } = useEditedComment({ comment });
// if the comment has a succeeded, failed or pending edit, use the edited comment
if (editedComment) {
comment = editedComment;
}
let editLabel;
if (editedCommentState === "succeeded") {
editLabel = { text: "EDITED", color: "green" };
}
if (editedCommentState === "pending") {
editLabel = { text: "PENDING EDIT", color: "orange" };
}
if (editedCommentState === "failed") {
editLabel = { text: "FAILED EDIT", color: "red" };
}You can remove comments from your local account database (local JSON export / IndexedDB state) in two ways. This only removes local account history entries; it does not delete already-published network comments.
1. Abandon a pending publish — if you just published and want to cancel before it propagates:
const { publishComment, abandonPublish } = usePublishComment(publishCommentOptions);
await publishComment();
// User changes mind — abandon the pending comment
await abandonPublish();
// Hook state returns to ready; the comment is removed from accountComments2. Delete by index or CID — remove any of your comments (pending or published):
import { deleteComment, useAccountComments } from "@bitsocialnet/bitsocial-react-hooks";
// By account comment index (from usePublishComment or useAccountComment)
const { index, publishComment } = usePublishComment(publishCommentOptions);
await publishComment();
await deleteComment(index);
// By comment CID (from useAccountComments or useAccountComment)
const { accountComments } = useAccountComments();
const accountComment = accountComments[0];
await deleteComment(accountComment.cid);Note:
accountComment.indexcan change after deletions. If you delete a comment, indices of comments after it may shift. Prefer usingcommentCidwhen you need a stable identifier, or re-fetchaccountCommentsafter deletions.
Common cleanup pattern (remove failed UI clutter):
import { deleteComment, useAccountComments } from "@bitsocialnet/bitsocial-react-hooks";
const { accountComments } = useAccountComments();
const failedComments = accountComments.filter((comment) => comment.state === "failed");
for (const failedComment of failedComments) {
// failed pending comments may not have a cid yet, so fallback to index
await deleteComment(failedComment.cid || failedComment.index);
}let communityAddress = "news.eth";
communityAddress = "12D3KooWANwdyPERMQaCgiMnTT1t3Lr4XLFbK1z4ptFVhW2ozg1z";
communityAddress = "tech.eth";
const { subscribed, subscribe, unsubscribe } = useSubscribe({ communityAddress });
await subscribe();
console.log(subscribed); // true
// view subscriptions
const account = useAccount();
console.log(account.subscriptions); // ['news.eth', '12D3KooWANwdyPERMQaCgiMnTT1t3Lr4XLFbK1z4ptFVhW2ozg1z', 'tech.eth']
// unsubscribe
await unsubscribe();
// get a feed of subscriptions
const { feed, hasMore, loadMore } = useFeed({
communityAddresses: account.subscriptions,
sortType: "topAll",
});
console.log(feed);import {Virtuoso} from 'react-virtuoso'
const {feed, hasMore, loadMore} = useFeed({communityAddresses: ['memes.eth', '12D3KooW...', '12D3KooW...'], sortType: 'topAll'})
<Virtuoso
data={feed}
itemContent={(index, post) => <Post index={index} post={post}/>}
useWindowScroll={true}
components={{Footer: hasMore ? () => <Loading/> : undefined}}
endReached={loadMore}
increaseViewportBy={{bottom: 600, top: 600}}
/>
// you probably will want to buffer some feeds in the background so they are already loaded
// when you need them
useBufferedFeeds({
feedsOptions: [
{communityAddresses: ['news.eth', 'crypto.eth'], sortType: 'new'},
{communityAddresses: ['memes.eth'], sortType: 'topWeek'},
{communityAddresses: ['12D3KooW...', '12D3KooW...', '12D3KooW...', '12D3KooW...'], sortType: 'hot'}
]
})
// search a feed
const createSearchFilter = (searchTerm) => ({
filter: (comment) => comment.title?.includes(searchTerm) || comment.content?.includes(searchTerm),
key: `includes-${searchTerm}` // required key to cache the filter
})
const filter = createSearchFilter('bitcoin')
const {feed, hasMore, loadMore} = useFeed({communityAddresses, filter})
// image only feed
const filter = {
filter: (comment) => getCommentLinkMediaType(comment?.link) === 'image',
key: 'image-only' // required key to cache the filter
}
const {feed, hasMore, loadMore} = useFeed({communityAddresses, filter})import {Virtuoso} from 'react-virtuoso'
const {feed, hasMore, loadMore} = useFeed({
communityAddresses: ['memes.eth', '12D3KooW...', '12D3KooW...'],
modQueue: ['pendingApproval']
})
<Virtuoso
data={feed}
itemContent={(index, post) => <Post index={index} post={post}/>}
useWindowScroll={true}
components={{Footer: hasMore ? () => <Loading/> : undefined}}
endReached={loadMore}
increaseViewportBy={{bottom: 600, top: 600}}
/>Comments automatically drop out of this feed once they are no longer returned by the pending-approval mod-queue pages.
const publishCommentModerationOptions = {
commentCid: "QmZVYzLChjKrYDVty6e5JokKffGDZivmEJz9318EYfp2ui",
communityAddress: "news.eth",
commentModeration: { approved: true },
onChallenge,
onChallengeVerification,
onError,
};
const { state, error, publishCommentModeration } = usePublishCommentModeration(
publishCommentModerationOptions,
);
await publishCommentModeration();
console.log(state);
console.log(error);import {useAccount, setAccount, useResolvedAuthorAddress} from '@bitsocialnet/bitsocial-react-hooks'
const account = useAccount() // or useAccount('Account 2') to use an account other than the active one
// `account.author.wallets` only auto-generates an `eth` wallet by default
console.log(account.author.wallets.eth)
const author: {...account.author, displayName: 'John'}
const editedAccount = {...account, author}
await setAccount(editedAccount)
// check if the user has set their .eth or .bso author name properly, use {cache: false} or it won't update
const author = {...account.author, address: 'username.bso'} // or 'username.eth'
// authorAddress should equal to account.signer.address
const {resolvedAddress, state, error, chainProvider} = useResolvedAuthorAddress({author, cache: false})
// result
if (state === 'succeeded') {
console.log('Succeeded resolving address', resolvedAddress)
}
if (state === 'failed') {
console.log('Failed resolving address', error.message)
}
// pending
if (state === 'resolving') {
console.log('Resolving address from chain provider URL', chainProvider.urls)
}Note: deleting account is unrecoverable, warn the user to export/backup his account before deleting
import { deleteAccount } from "@bitsocialnet/bitsocial-react-hooks";
// delete active account
await deleteAccount();
// delete account by name
await deleteAccount("Account 2");// all my own comments
const { accountComments } = useAccountComments();
for (const accountComment of accountComments) {
// it is recommended to show a label in the UI if accountComment.state is 'pending' or 'failed'
console.log("comment", accountComment.index, "is status", accountComment.state);
}
// note: accountComment.index can change after deletions; prefer commentCid for stable identifiers
// all my own votes
const { accountVotes } = useAccountVotes();
// my own comments in memes.eth
const communityAddress = "memes.eth";
const filter = useCallback(
(comment) => comment.communityAddress === communityAddress,
[communityAddress],
); // important to use useCallback or the same function or will cause rerenders
const myCommentsInMemesEth = useAccountComments({ filter });
// my own posts in memes.eth
const filter = useCallback(
(comment) => comment.communityAddress === communityAddress && !comment.parentCid,
[communityAddress],
);
const myPostsInMemesEth = useAccountComments({ filter });
// my own replies in a post with cid 'Qm...'
const postCid = "Qm...";
const filter = useCallback((comment) => comment.postCid === postCid, [postCid]);
const myCommentsInSomePost = useAccountComments({ filter });
// my own replies to a comment with cid 'Qm...'
const parentCommentCid = "Qm...";
const filter = useCallback((comment) => comment.parentCid === parentCommentCid, [parentCommentCid]);
const myRepliesToSomeComment = useAccountComments({ filter });
// know if you upvoted a comment already with cid 'Qm...'
const { vote } = useAccountVote({ commentCid: "Qm..." });
console.log(vote); // 1, -1 or 0
// my own pending posts in a feed
const { feed } = useFeed({
communityAddresses: [communityAddress],
accountComments: { newerThan: Infinity, append: false },
});
// my own pending replies in a replies feed
const { replies } = useReplies({
comment: post,
accountComments: { newerThan: Infinity, append: false },
});const account = useAccount();
const comment = useComment({ commentCid });
const isMyOwnComment = account?.author.address === comment?.author.address;const { notifications, markAsRead } = useNotifications();
for (const notification of notifications) {
console.log(notification);
}
await markAsRead();
const johnsNotifications = useNotifications({ accountName: "John" });
for (const notification of johnsNotifications.notifications) {
console.log(notification);
}
await johnsNotifications.markAsRead();
// get the unread notification counts for all accounts
const { accounts } = useAccounts();
const accountsUnreadNotificationsCounts = accounts?.map(
(account) => account.unreadNotificationCount,
);const address: 'community-address.eth' // or 'author-address.eth' or '12D3KooW...'
const {blocked, unblock, block} = useBlock({address})
if (blocked) {
console.log(`'${address}' is blocked`)
}
else {
console.log(`'${address}' is not blocked`)
}
// to block
block()
// to unblock
unblock()const { blocked, unblock, block } = useBlock({ cid: "Qm..." });
if (blocked) {
console.log(`'${cid}' is blocked`);
} else {
console.log(`'${cid}' is not blocked`);
}
// to block
block();
// to unblock
unblock();const createCommunityOptions = { title: "My community title" };
const { createdCommunity, createCommunity } = useCreateCommunity(createCommunityOptions);
await createCommunity();
// it is recommended to redirect to `p/${createdCommunity.address}` after creation
if (createdCommunity?.address) {
console.log("created community with title", createdCommunity.title);
history.push(`/p/${createdCommunity.address}`);
}
// after the community is created, fetch it using
const { accountCommunities } = useAccountCommunities();
const accountCommunityAddresses = Object.keys(accountCommunities);
const communities = useCommunities({ communityAddresses: accountCommunityAddresses });
// or
const _community = useCommunity({ communityAddress: createdCommunity.address });const { accountCommunities } = useAccountCommunities();
const ownerCommunityAddresses = Object.keys(accountCommunities).filter(
(communityAddress) => accountCommunities[communityAddress].role?.role === "owner",
);
const communities = useCommunities({ communityAddresses: ownerCommunityAddresses });const onChallenge = async (challenges: Challenge[], communityEdit: CommunityEdit) => {
let challengeAnswers: string[]
try {
challengeAnswers = await getChallengeAnswersFromUser(challenges)
}
catch (e) {}
if (challengeAnswers) {
await communityEdit.publishChallengeAnswers(challengeAnswers)
}
}
const onChallengeVerification = (challengeVerification, communityEdit) => {
console.log('challenge verified', challengeVerification)
}
const onError = (error, communityEdit) => console.error(error)
// add ENS to your community
const editCommunityOptions = {
communityAddress: '12D3KooWANwdyPERMQaCgiMnTT1t3Lr4XLFbK1z4ptFVhW2ozg1z', // the previous address before changing it
address: 'your-community-address.eth', // the new address to change to
onChallenge,
onChallengeVerification,
onError
}
await publishCommunityEdit()
// edit other community settings
const editCommunityOptions = {
communityAddress: 'your-community-address.eth', // the address of the community to change
title: 'Your title',
description: 'Your description',
onChallenge,
onChallengeVerification,
onError
}
const {publishCommunityEdit} = usePublishCommunityEdit(editCommunityOptions)
await publishCommunityEdit()
// verify if ENS was set correctly, use {cache: false} or it won't update
const {resolvedAddress} = useResolvedCommunityAddress({communityAddress: 'your-community-address.eth', cache: false})
// result
if (state === 'succeeded') {
console.log('Succeeded resolving address', resolvedAddress)
console.log('ENS set correctly', resolvedAddress === community.signer.address)
}
if (state === 'failed') {
console.log('Failed resolving address', error.message)
}
// pending
if (state === 'resolving') {
console.log('Resolving address from chain provider URL', chainProvider.urls)
}import {
exportAccount,
importAccount,
setActiveAccount,
setAccountsOrder,
} from "@bitsocialnet/bitsocial-react-hooks";
// get active account 'Account 1'
const activeAccount = useAccount();
// export active account, tell user to copy or download this json
const activeAccountJson = await exportAccount();
// import account
await importAccount(activeAccountJson);
// get imported account 'Account 1 2' (' 2' gets added to account.name if account.name already exists)
const importedAccount = useAccount("Account 1 2");
// make imported account active account
await setActiveAccount("Account 1 2");
// reorder the accounts list
await setAccountsOrder(["Account 1 2", "Account 1"]);let comment = useComment({ commentCid });
const { state: editedCommentState, editedComment } = useEditedComment({ comment });
// if the comment has a succeeded, failed or pending edit, use the edited comment
if (editedComment) {
comment = editedComment;
}
let editLabel;
if (editedCommentState === "succeeded") {
editLabel = { text: "EDITED", color: "green" };
}
if (editedCommentState === "pending") {
editLabel = { text: "PENDING EDIT", color: "orange" };
}
if (editedCommentState === "failed") {
editLabel = { text: "FAILED EDIT", color: "red" };
}const comment = useComment({ commentCid });
const editedComment = useEditedComment({ comment });
if (editedComment.failedEdits.removed !== undefined) {
console.log("failed editing comment.removed property");
}
if (editedComment.succeededEdits.removed !== undefined) {
console.log("succeeded editing comment.removed property");
}
if (editedCommentResult.pendingEdits.removed !== undefined) {
console.log("pending editing comment.removed property");
}
// view the full comment with all edited properties (both succeeded and pending)
console.log(editedComment.editedComment);
console.log(editedComment.editedComment.commentModeration?.removed);
// view the state of all edits of the comment
console.log(editedComment.state); // 'unedited' | 'succeeded' | 'pending' | 'failed'Moderation fields are mirrored on both the legacy top-level keys like comment.removed and the nested comment.commentModeration.removed shape.
const { accountEdits } = useAccountEdits();
for (const accountEdit of accountEdits) {
console.log(accountEdit);
}
console.log(`there's ${accountEdits.length} account edits`);
// get only the account edits of a specific comment
const commentCid = "Qm...";
const filter = useCallback((edit) => edit.commentCid === commentCid, [commentCid]); // important to use useMemo or the same function or will cause rerenders
const { accountEdits } = useAccountEdits({ filter });
// only get account edits in a specific community
const communityAddress = "news.eth";
const filter = useCallback(
(edit) => edit.communityAddress === communityAddress,
[communityAddress],
);
const { accountEdits } = useAccountEdits({ filter });import { useReplies, useComment, useAccountComment } from "@bitsocialnet/bitsocial-react-hooks";
// NOTE: recommended to use the same replies options for all depths, or will load slower
const useRepliesOptions = {
sortType: "best",
flat: false,
repliesPerPage: 20,
accountComments: { newerThan: Infinity, append: false },
};
const Reply = ({ reply, updatedReply }) => {
const { replies, updatedReplies, bufferedReplies, hasMore, loadMore } = useReplies({
...useRepliesOptions,
comment: reply,
});
// updatedReply updates values in real time, reply does not
const score = (updatedReply?.upvoteCount || 0) - (updatedReply?.downvoteCount || 0);
// bufferedReplies updates in real time, can show new replies count in real time
const moreReplies =
hasMore && bufferedReplies?.length !== 0 ? `(${bufferedReplies.length} more replies)` : "";
// publishing states exist only on account comment
const accountReply = useAccountComment({ commentIndex: reply.index });
const state = accountReply?.state;
const publishingStateString = useStateString(accountReply);
return (
<div>
<div>
{score} {reply.author.address} {reply.timestamp} {moreReplies}
</div>
{state === "pending" && <div>PENDING ({publishingStateString})</div>}
{state === "failed" && <div>FAILED</div>}
<div>{reply.content}</div>
<div style={{ marginLeft: 4 }}>
{replies.map((reply, index) => (
<Reply
key={reply?.index || reply?.cid}
reply={reply}
updatedReply={updatedReplies[index]}
/>
))}
</div>
</div>
);
};
const comment = useComment({ commentCid });
const { replies, updatedReplies, hasMore, loadMore } = useReplies({
...useRepliesOptions,
comment,
});
const repliesComponents = replies.map((reply, index) => (
<Reply key={reply?.index || reply?.cid} reply={reply} updatedReply={updatedReplies[index]} />
));// NOTE: not possible to do from bitsocial-react-hooks, needs plebbit-js
import { getShortAddress, getShortCid } from "@plebbit/plebbit-js";
const shortParentCid = getShortAddress(comment.parentCid);
const shortAddress = getShortCid(address);import { useShortAddress, useShortCid } from "@bitsocialnet/bitsocial-react-hooks";
const shortParentCid = useShortCid(comment.parentCid);
const shortAddress = useShortAddress(address);const useBufferedFeedsWithConcurrency = ({feedOptions}) => {
const communities = useCommunities()
return useBufferedFeeds({feedsOptions})
}
const feedOptions = [
{communityAddresses: ['news.eth', 'crypto.eth'], sortType: 'new'},
{communityAddresses: ['memes.eth'], sortType: 'topWeek'},
{communityAddresses: ['12D3KooW...', '12D3KooW...', '12D3KooW...', '12D3KooW...'], sortType: 'hot'},
...
]
useBufferedFeedsWithConcurrency({feedOptions})