Fostering meaningful conversations within your app can make or break user engagement. Whether it's a blog, a social media platform, or an e-commerce app, providing users with a space to share their thoughts and connect with others elevates the experience to a whole new level. That's where a modern, fully-featured comment section comes in.
In this article, I'll show you exactly how to integrate a comment section into your ReactJS app. For this guide I will be using Vite, but exactly the same code could be used in a NextJS or other ReactJS based framework
If you are a mobile developer, I also wrote a similar guide for implementation in a React Native app. You can check it out over here.
By the end of this guide, you'll have a dynamic comment section up and running in no time.
For the comment section itself, we’ll be using Replyke, a versatile tool that brings a host of exciting features to the table:
- Mentions to tag users directly.
- Threaded replies to keep conversations structured.
- Likes for instant feedback.
- Highlighted comments and replies for emphasis.
- GIF support to add personality.
- Built-in authorization to ensure only authenticated users can interact.
- User reporting to flag inappropriate content.
- A powerful back office for moderation, allowing you to easily manage flagged content and take action when needed.
These features make Replyke more than just a comment section—it’s a tool for building community and trust within your app.
To keep things simple and focused, we’ll use static dummy data for our entities (posts or articles) and a logged-in user. In a production environment, you’d connect this to your dynamic content and user management system, but the implementation steps remain identical.
Why is adding a comment section so important? Beyond giving users a voice, it opens the door to richer interactions, improved retention, and higher app engagement. Plus, with Replyke’s moderation features baked in, you can ensure your app remains a safe and welcoming space for everyone. Moderation is often an afterthought, but it’s essential for managing user-generated content, and having a back office built right in is a game-changer.
This article is a step aside from my ongoing series, "How to Build a Social Network in 1 Day." If you’re as excited as I am about the power of Replyke, I encourage you to check out that series too. But for now, let’s dive in and create an engaging, fully-functional comment section for your app!
Part 1 is for basic setup for those who start from scratch for the sake of this tutorial. If you already have an existing app you can skip this step. That said, it is recommended to follow this guide as a separate app for a smooth experience first, and simply copy any code to your own app when done.
At the end of every major milestone you would find a link to GitHub with the source code up to that point.
Part 1: Basic repository setup
Setting Up the Project
To get started, navigate to the directory where you want to create your repository. Open your terminal and run the following command:
npm create vite@latest comments-tutorial -- --template react-ts
This initializes a new React project with TypeScript using Vite in your chosen directory.
Next, navigate into the newly created repository and install the necessary dependencies:
cd comments-tutorial
npm install
Cleaning Up the Project Structure
Before proceeding, let’s clean up unnecessary files:
-
Delete the following files:
- Any files inside the
public/
directory - Any files inside
src/assets/
src/App.css
- Any files inside the
-
Clear the contents of these files (but keep them):
src/index.css
src/App.tsx
After clearing src/App.tsx
, it should look like this:
function App() {
return <div>Hello World</div>;
}
export default App;
Installing Tailwind CSS
For easy styling, install Tailwind CSS by running:
npm install tailwindcss @tailwindcss/vite
Next, update your Vite configuration to include Tailwind CSS. Open vite.config.ts
and modify it to look like this:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()]
});
Then, add the following import statement to your empty src/index.css
file:
@import "tailwindcss";
Now, verify Tailwind is working by updating src/App.tsx
:
function App() {
return <div className="min-h-screen bg-red-500">Hello World</div>;
}
export default App;
Run the development server:
npm run dev
Open your browser, and you should see a red screen with "Hello World" in the top-left corner. If not, double-check the steps above or refer to the official Tailwind CSS documentation for any recent changes.
Setting Up ShadCN
Before proceeding with Replyke, let's configure ShadCN for UI components.
Modify tsconfig.json
by adding the following inside compilerOptions
at the root level (as a sibling of references
):
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
Modify tsconfig.app.json
by adding the following under compilerOptions
:
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
Install Node types as a dev dependency:
npm install -D @types/node
Update vite.config.ts
to resolve alias paths. Your final file should look like this:
import path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
Initialize ShadCN:
npx shadcn@latest init
You can simply use all the default values during the setup.
Lastly, add the components we will need by running
npx shadcn@latest add button drawer scroll-area sheet sonner
That’s it for the setup! You can check the repository state at this point.
Part 1 completed source code on GitHub for reference.
Part 2: Setting Up Replyke in Your Project
With our basic app setup complete and running, it's time to integrate Replyke and bring our comment section to life. This involves setting up a new Replyke project and adding its provider to our app. Unlike full-scale authentication setups, we'll be using a mock external user system for this tutorial. Replyke expects a signedToken
prop containing a signed JWT with user details, and we’ll configure this later. For now, let’s focus on the foundational setup of Replyke itself.
Steps:
Create a Project on Replyke Dashboard: Head over to dashboard.replyke.com, create an account if you don’t already have one, and start a new project. Once the project is created, copy the provided Project ID into a
.env
file.-
Install Replyke for ReactJS: Open your terminal and run the following command to install Replyke for your Expo project:
npm install @replyke/react-js
Wrap Your App with
ReplykeProvider
: In yoursrc/App.tsx
file, wrap your app’s content with theReplykeProvider
component from Replyke. Pass the Project ID you copied earlier as theprojectId
prop. This initializes Replyke within your app.
Here’s how your src/App.tsx
file should look like after making these changes (we're also adding the sonner Toaster while we're at it):
import { ReplykeProvider } from "@replyke/react-js";
import { Toaster } from "@/components/ui/sonner";
function App() {
return (
<ReplykeProvider projectId={import.meta.env.VITE_PUBLIC_REPLYKE_PROJECT_ID}>
<Toaster />
<div>Hello world</div>
</ReplykeProvider>
);
}
export default App;
With these steps completed, Replyke is now set up and ready to power your comment section.
Part 3: Creating Dummy Data
To test our comment section, we’ll need some sample data for posts and users. This will allow us to simulate the interaction between users and the comment system. Let’s create a file to store our dummy data.
Create a Constants Folder: Inside your project’s
src
directory, create a folder namedconstants
.Add the Dummy Data File: In the
constants
folder, create a new file calleddummy-data.ts
and paste the following code:
// Dummy data for posts
const posts = [
{ id: "post1", content: "This is the first post." },
{ id: "post2", content: "Hello world! This is post two." },
{ id: "post3", content: "Another day, another post." },
{ id: "post4", content: "Simple content for post four." },
{ id: "post5", content: "Post five is here." },
{ id: "post6", content: "Sharing thoughts in post six." },
{ id: "post7", content: "This is the seventh post." },
{ id: "post8", content: "Post eight with simple text." },
{ id: "post9", content: "The ninth post is ready." },
{ id: "post10", content: "Finally, this is the tenth post." },
];
// Dummy data for users
const users = [
{ id: "user1", username: "john123" },
{ id: "user2", username: "emma456" },
{ id: "user3", username: "mike789" },
{ id: "user4", username: "sarah202" },
];
export { posts, users };
- Save and Import the Dummy Data: With the dummy data file in place, we can now use these posts and users in our app to simulate interactions.
Render posts
Lastly, let's present our dummy posts in our home screen in a nice grid. Replace the contents of src/App.tsx
with the following:
import { ReplykeProvider } from "@replyke/react-js";
import { Toaster } from "@/components/ui/sonner";
import { posts } from "./constants/dummy-data";
function App() {
return (
<ReplykeProvider projectId={import.meta.env.VITE_PUBLIC_REPLYKE_PROJECT_ID}>
<Toaster />
<div className="h-screen grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 p-6 sm:p-8 md:p-12 lg:p-24 xl:p-52 gap-4 md:gap-8 lg:gap-12 m-auto">
{posts.map((post) => (
<div className="bg-white p-4 mb-4 rounded-xl shadow-lg">
{/* Post Content */}
<p className="text-lg font-bold text-gray-800">{post.id}</p>
<p className="text-gray-600 mt-2">{post.content}</p>
{/* Open Discussion Button */}
<Button className="w-full mt-4 cursor-pointer">
Open Discussion
</Button>
</div>
))}
</div>
</ReplykeProvider>
);
}
export default App;
You should now see all of our dummy posts when you open the app.
Parts 2 and 3 completed source code on GitHub for reference.
Part 4: Adding the Comment Section
With our app setup complete and displaying posts, it’s time to integrate the Replyke comment section. This will allow users to engage in discussions on each post.
Installing the Social Comment Package
To begin, install the Replyke social comments package:
npm install @replyke/comments-social-react-js
Ensuring Responsiveness
We want our app to be fully responsive. To achieve this, we’ll use both the Drawer
and Sheet
components from ShadCN, displaying one or the other depending on the screen size.
To implement this, we first need a utility hook to detect screen size.
Creating a Media Query Hook
Inside the src
folder, create a new directory called hooks
, and within it, create a file named useMediaQuery.tsx
. Add the following code:
import { useState, useEffect } from "react";
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => setMatches(media.matches);
window.addEventListener("resize", listener);
return () => window.removeEventListener("resize", listener);
}, [matches, query]);
return matches;
}
This hook allows us to detect whether the screen size meets a specified media query.
Creating the DiscussionSheet Component
Now, let’s create the comment section component. Inside the src/components
directory, create a file named DiscussionSheet.tsx
and add the following:
import { useEntity } from "@replyke/react-js";
import {
SocialStyleCallbacks,
useSocialComments,
useSocialStyle,
} from "@replyke/comments-social-react-js";
import { toast } from "sonner";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { Drawer, DrawerContent } from "@/components/ui/drawer";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useMediaQuery } from "@/hooks/useMediaQuery";
export function DiscussionSheet({
isSheetOpen,
onClose,
}: {
isSheetOpen: boolean;
onClose: () => void;
}) {
const isDesktop = useMediaQuery("(min-width: 768px)");
const { entity } = useEntity();
const callbacks: SocialStyleCallbacks = {
loginRequiredCallback: () => {
toast("Please log in first");
},
// More callbacks can be used to further customize the comment section’s behavior.
};
const styleConfig = useSocialStyle(); // This is required, even if using default styles.
const { CommentSectionProvider, SortByButton, CommentsFeed, NewCommentForm } =
useSocialComments({
entityId: entity?.id,
styleConfig,
callbacks,
});
const sortByOptions = (
<div className="flex px-6 items-center gap-1">
<h4 className="font-semibold text-base flex-1">Comments</h4>
<SortByButton priority="top" activeView={<div className="bg-black py-1 px-2 rounded-md text-white text-xs">Top</div>} nonActiveView={<div className="bg-gray-200 py-1 px-2 rounded-md text-xs">Top</div>} />
<SortByButton priority="new" activeView={<div className="bg-black py-1 px-2 rounded-md text-white text-xs">New</div>} nonActiveView={<div className="bg-gray-200 py-1 px-2 rounded-md text-xs">New</div>} />
<SortByButton priority="old" activeView={<div className="bg-black py-1 px-2 rounded-md text-white text-xs">Old</div>} nonActiveView={<div className="bg-gray-200 py-1 px-2 rounded-md text-xs">Old</div>} />
</div>
);
const mobileSection = (
<Drawer open={isSheetOpen} onOpenChange={(state) => !state && onClose()}>
<DrawerContent className="h-screen overflow-hidden flex flex-col p-0 pt-6 bg-white gap-3">
<CommentSectionProvider>
{sortByOptions}
<ScrollArea className="flex-1 bg-white">
<CommentsFeed />
</ScrollArea>
<div className="border-t">{isSheetOpen && <NewCommentForm />}</div>
</CommentSectionProvider>
</DrawerContent>
</Drawer>
);
const desktopSection = (
<Sheet open={isSheetOpen} onOpenChange={(state) => !state && onClose()}>
<SheetContent className="h-screen overflow-hidden flex flex-col p-0 pt-6 bg-white">
<CommentSectionProvider>
{sortByOptions}
<ScrollArea className="flex-1 bg-white">
<CommentsFeed />
</ScrollArea>
<div className="border-t">{isSheetOpen && <NewCommentForm />}</div>
</CommentSectionProvider>
</SheetContent>
</Sheet>
);
return <div className="relative">{isDesktop ? desktopSection : mobileSection}</div>;
}
export default DiscussionSheet;
Explanation of DiscussionSheet Component
- useEntity - This hook provides the entity context, which in our app is a "post". The connection between Replyke and our posts will be clear once we integrate this component into our App.
- useSocialStyle - This hook is required to pass a style configuration object, even if we are using the default styles.
-
Callbacks - The
loginRequiredCallback
function notifies users when authentication is needed before interacting. There are additional callbacks available to further customize the behavior of the comment section. - useSocialComments - This hook generates the building blocks for the comment section, including the provider, sorting buttons, and comment components.
- SortByButton - These buttons allow sorting comments by "Top", "New", and "Old".
-
Responsive Layout - The component conditionally renders a
Drawer
for mobile and aSheet
for desktop, usinguseMediaQuery
.
Updating the App Component
The final step is integrating DiscussionSheet
in App.tsx
. Replace its content with:
import { useState } from "react";
import { EntityProvider, ReplykeProvider } from "@replyke/react-js";
import { Toaster } from "@/components/ui/sonner";
import { posts } from "./constants/dummy-data";
import { Button } from "./components/ui/button";
import DiscussionSheet from "./components/DiscussionSheet";
function App() {
const [selectedPostId, setSelectdPostId] = useState<string | null>(null);
return (
<ReplykeProvider projectId={import.meta.env.VITE_PUBLIC_REPLYKE_PROJECT_ID}>
<Toaster />
<EntityProvider referenceId={selectedPostId} createIfNotFound>
<DiscussionSheet
isSheetOpen={!!selectedPostId}
onClose={() => setSelectdPostId(null)}
/>
</EntityProvider>
<div className="h-screen grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 p-6 sm:p-8 md:p-12 lg:p-24 xl:p-52 gap-4 md:gap-8 lg:gap-12 m-auto">
{posts.map((post) => (
<div className="bg-white p-4 mb-4 rounded-xl shadow-lg" key={post.id}>
{/* Post Content */}
<p className="text-lg font-bold text-gray-800">{post.id}</p>
<p className="text-gray-600 mt-2">{post.content}</p>
{/* Open Discussion Button */}
<Button
onClick={() => setSelectdPostId(post.id)}
className="w-full mt-4 cursor-pointer"
>
Open Discussion
</Button>
</div>
))}
</div>
</ReplykeProvider>
);
}
export default App;
Explanation of App Component Changes
- selectedPostId - This state determines which post’s comment section is open.
-
EntityProvider - Wraps
DiscussionSheet
, linking the Replyke comment system to our posts. -
referenceId - Instead of
entityId
, we usereferenceId
, allowing Replyke to link our existing post IDs to its entities. - createIfNotFound - Ensures an entity is created if it doesn’t exist.
-
Open Discussion Button - Clicking it sets
selectedPostId
, opening the discussion sheet.
Now, clicking “Open Discussion” will open the comment section for that specific post!
Part 4 completed source code on GitHub for reference.
Part 5: Integrating Authentication with Replyke
At the end of Part 4, we successfully set up a functional comment section, but we couldn't interact with it because we weren't authenticated. Replyke's comment section requires information about the logged-in user to function properly. This final part focuses on integrating authentication into our app, enabling users to leave comments and interact with the system as authenticated users.
Understanding Replyke's Authentication
Replyke offers a simple yet robust mechanism to integrate authentication. Here's how it works:
-
JWT Keys: Replyke requires project owners to generate a JWT key pair via the Replyke dashboard. This generates:
- A secret key (only visible once) for signing JWTs.
- A public key, which Replyke keeps and associates with the project for verification.
JWT Signing: Developers use the secret key to sign a JWT with logged-in user details. This JWT is passed to Replyke to verify its validity and trustworthiness.
Authenticated Actions: Once verified, Replyke associates the actions (comments, likes, etc.) with the authenticated user.
Critical Note on Security
- Never (NEVER) store or use the secret key on the client side in production. Signing JWTs client-side is a major security risk.
- In production, always implement JWT signing on your server. Replyke’s documentation provides detailed guidance on this.
- For this tutorial, we’ll demonstrate a simplified implementation for testing and educational purposes only, using Replyke’s
useSignTestingJwt
hook.
Setting Up Environment Variables
-
Add your secret key as an environment variable in Expo:
EXPO_PUBLIC_REPLYKE_SECRET_KEY=your-secret-key EXPO_PUBLIC_REPLYKE_PROJECT_ID=your-project-id
Use these variables in your
src/App.tsx
file for authentication setup.
Updated src/App.tsx
import { useEffect, useState } from "react";
import {
EntityProvider,
ReplykeProvider,
useSignTestingJwt,
} from "@replyke/react-js";
import { Toaster } from "@/components/ui/sonner";
import { posts, users } from "./constants/dummy-data";
import { Button } from "./components/ui/button";
import DiscussionSheet from "./components/DiscussionSheet";
const PROJECT_ID = import.meta.env.VITE_PUBLIC_REPLYKE_PROJECT_ID;
const PRIVATE_KEY = import.meta.env.VITE_PUBLIC_REPLYKE_SECRET_KEY;
function App() {
const signTestingJwt = useSignTestingJwt();
const [selectedPostId, setSelectdPostId] = useState<string | null>(null);
const [signedToken, setSignedToken] = useState<string>();
useEffect(() => {
const handleSignJwt = async () => {
const payload = users[0];
const token = await signTestingJwt({
projectId: PROJECT_ID,
payload,
privateKey: PRIVATE_KEY,
});
// Set the signed JWT in the state
setSignedToken(token);
};
handleSignJwt();
}, []);
return (
<ReplykeProvider projectId={PROJECT_ID} signedToken={signedToken}>
<Toaster />
<EntityProvider referenceId={selectedPostId} createIfNotFound>
<DiscussionSheet
isSheetOpen={!!selectedPostId}
onClose={() => setSelectdPostId(null)}
/>
</EntityProvider>
<div className="h-screen grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 p-6 sm:p-8 md:p-12 lg:p-24 xl:p-52 gap-4 md:gap-8 lg:gap-12 m-auto">
{posts.map((post) => (
<div className="bg-white p-4 mb-4 rounded-xl shadow-lg" key={post.id}>
{/* Post Content */}
<p className="text-lg font-bold text-gray-800">{post.id}</p>
<p className="text-gray-600 mt-2">{post.content}</p>
{/* Open Discussion Button */}
<Button
onClick={() => setSelectdPostId(post.id)}
className="w-full mt-4 cursor-pointer"
>
Open Discussion
</Button>
</div>
))}
</div>
</ReplykeProvider>
);
}
export default App;
Logging Out
When a user signs out, we need to notify Replyke to clear the authenticated state. Use the signOut
function from the useAuth
hook:
import { useAuth } from "@replyke/react-js";
const { signOut } = useAuth();
signOut?.();
This function should be called whenever a user logs out, and it is only available within the ReplykeProvider
context.
Key Rotation
If you’ve tested this implementation and later move JWT signing to the server, rotate your secret keys immediately to ensure security. Any key exposed on the client side is compromised and should not be used in production. Regardless, it is a good habit to rotate keys periodically.
Reports and Moderation Tools
Replyke also provides built-in reporting and moderation tools to ensure a safe and respectful community. If a user long-presses on a comment by another user, they can report it. Any reported comment will appear in the project’s dashboard on Replyke for moderation. This seamless integration allows developers to monitor and manage user-generated content effectively.
With this setup, your app can now handle authenticated interactions with Replyke's comment system, providing users with a seamless experience while maintaining a secure foundation. From here, you can explore further customization and server-side integrations for a production-ready implementation.
Part 5 completed source code on GitHub for reference.
Bonus Parts
This article doesn’t end here. In two bonus parts, we will explore how to:
Add GIF Functionality: Enhance user interactions by allowing them to post comments with GIFs, making conversations more engaging and lively.
Add Likes to Posts: Implement a simple and efficient like functionality for posts, encouraging user interaction and providing instant feedback.
Both features are super easy to implement using Replyke’s tools. Let’s dive into them next!
Bonus Part 1: Adding GIF Functionality
Enhancing user interactions with GIFs is incredibly easy using Replyke. Here's how:
Get a GIPHY API Key: Go to GIPHY and create a new API key.
Enable GIFs in Replyke:
Copy your API key.
Go to your project dashboard on Replyke.
Activate the GIF feature and paste your API key.
That's it! GIF support will now be enabled in your comment section, allowing users to search and post GIFs directly.
Bonus Part 2: Adding Likes to Posts
Implementing likes for posts is just as straightforward. Here's how:
- Create a
SinglePost
Component: Create a newSinglePost
component in yourcomponents
directory. It will hold our previous UI logic for each post plus newly added buttons for voting.
import { useEntity, useUser } from "@replyke/react-js";
import { toast } from "sonner";
import { Button } from "./ui/button";
import { cn } from "../lib/utils";
const SinglePost = ({
handleOpenDiscussionSheet,
}: {
handleOpenDiscussionSheet: () => void;
}) => {
const { user } = useUser();
const {
entity,
userDownvotedEntity,
userUpvotedEntity,
upvoteEntity,
removeEntityUpvote,
downvoteEntity,
removeEntityDownvote,
} = useEntity();
const upvotesCount = entity?.upvotes.length || 0;
const downvotesCount = entity?.downvotes.length || 0;
const handleUpvote = () => {
if (!user) return toast("Please login first");
if (userUpvotedEntity) {
removeEntityUpvote?.();
} else {
upvoteEntity?.();
}
};
const handleDownvote = () => {
if (!user) return toast("Please login first");
if (userDownvotedEntity) {
removeEntityDownvote?.();
} else {
downvoteEntity?.();
}
};
return (
<div className="bg-white p-4 mb-4 rounded-xl shadow-lg">
{/* Post Content */}
<p className="text-lg font-bold text-gray-800">{entity?.referenceId}</p>
<p className="text-gray-600 mt-2">{entity?.content}</p>
{/* Voting Section */}
<div className="grid grid-cols-2 gap-2 items-center mt-4">
{/* Upvote Button */}
<Button
onClick={handleUpvote}
size="sm"
className={cn(
"cursor-pointer text-xs",
userUpvotedEntity
? "bg-green-500 hover:bg-green-500 text-white"
: "bg-gray-200 hover:bg-gray-200 text-gray-800"
)}
>
{userUpvotedEntity ? "Upvoted" : "Upvote"}
<span>({upvotesCount})</span>
</Button>
{/* Downvote Button */}
<Button
onClick={handleDownvote}
size="sm"
className={cn(
"cursor-pointer text-xs",
userDownvotedEntity
? "bg-red-500 hover:bg-red-500 text-white"
: "bg-gray-200 hover:bg-gray-200 text-gray-800"
)}
>
{userDownvotedEntity ? "Downvoted" : "Downvote"}
<span>({downvotesCount})</span>
</Button>
</div>
{/* Open Discussion Button */}
<Button
onClick={handleOpenDiscussionSheet}
className="w-full mt-4 cursor-pointer"
>
Open Discussion{" "}
{(entity?.repliesCount || 0) > 0 && `(${entity?.repliesCount})`}
</Button>
</div>
);
};
export default SinglePost;
- Wrap Each Post with an
EntityProvider
: Update thesrc/App.tsx
file to return the newSinglePost
component, with each post wrapped in anEntityProvider
.
import { useEffect, useState } from "react";
import {
EntityProvider,
ReplykeProvider,
useSignTestingJwt,
} from "@replyke/react-js";
import { Toaster } from "@/components/ui/sonner";
import { posts, users } from "./constants/dummy-data";
import DiscussionSheet from "./components/DiscussionSheet";
import SinglePost from "./components/SinglePost";
const PROJECT_ID = import.meta.env.VITE_PUBLIC_REPLYKE_PROJECT_ID;
const PRIVATE_KEY = import.meta.env.VITE_PUBLIC_REPLYKE_SECRET_KEY;
function App() {
const signTestingJwt = useSignTestingJwt();
const [selectedPostId, setSelectdPostId] = useState<string | null>(null);
const [signedToken, setSignedToken] = useState<string>();
useEffect(() => {
const handleSignJwt = async () => {
const payload = users[0];
const token = await signTestingJwt({
projectId: PROJECT_ID,
payload,
privateKey: PRIVATE_KEY,
});
// Set the signed JWT in the state
setSignedToken(token);
};
handleSignJwt();
}, []);
return (
<ReplykeProvider projectId={PROJECT_ID} signedToken={signedToken}>
<Toaster />
<EntityProvider referenceId={selectedPostId} createIfNotFound>
<DiscussionSheet
isSheetOpen={!!selectedPostId}
onClose={() => setSelectdPostId(null)}
/>
</EntityProvider>
<div className="h-screen grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 p-6 sm:p-8 md:p-12 lg:p-24 xl:p-52 gap-4 md:gap-8 lg:gap-12 m-auto">
{posts.map((post) => (
<EntityProvider referenceId={post.id} key={post.id}>
<SinglePost
handleOpenDiscussionSheet={() => setSelectdPostId(post.id)}
/>
</EntityProvider>
))}
</div>
</ReplykeProvider>
);
}
export default App;
That's it! Your posts now support upvotes and downvotes.
Part 6 completed source code on GitHub for reference.
Wrapping Up
In this article, we’ve covered setting up a fully functional comment section, adding authentication, enabling GIFs, and implementing voting functionality for posts. These features were straightforward to integrate, showcasing Replyke’s power and simplicity.
If you found this interesting, explore the rest of what Replyke has to offer, including advanced feeds, app notifications, user-generated lists, profiles, and much more. All are as easy to implement as the features we’ve covered here!
Stay Updated
Don’t forget to join the Discord server where I post free boilerplate code repos for different types of social networks. Updates about the article tutorials and additional resources will also be shared there. Lastly, follow me for updates here and on X/Twitter & BlueSky.
Top comments (0)