In today's digital age, 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 React Native app using an Expo-managed workflow. But here's the kicker—this implementation works just as seamlessly in a CLI workflow too. By the end of this guide, you'll have a dynamic comment section up and running in no time, leveraging the power of Expo, TypeScript, and NativeWind for styling.
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, 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
Step 1: Creating the Expo App
To begin, navigate to the directory where you want to create your repository. Open your terminal and run the following command:
npx create-expo-app@latest
Follow the prompts to configure your project. Once the setup is complete, navigate into the newly created repository:
cd <your-repo-name>
Open the project in your IDE of choice, such as Visual Studio Code.
Step 2: Cleaning Up the Project Structure
We want to start with a clean slate, so let’s tidy up the default files and folders created by Expo.
Remove Unnecessary Files
Components, Constants, and Hooks: Delete the contents of the
components
,constants
, andhooks
folders.Scripts Folder: Delete the
scripts
folder located at the root of your project (which containsreset-project.js
).
Clean Up Screens in the app
Folder
Inside the
app
folder, remove screens you won’t be using. Remove the (tabs) folder completely.For the remaining screens, clear out their contents, leaving placeholders or empty functional components as needed.
Final Folder Structure
After this cleanup, your project structure should look like this:
.gitignore
app
├─ _layout.tsx
└─ index.tsx
app.json
assets
├─ fonts
│ └─ SpaceMono-Regular.ttf
└─ images
├─ adaptive-icon.png
├─ icon.png
└─ splash-icon.png
components
constants
hooks
package-lock.json
package.json
README.md
tsconfig.json
This streamlined setup ensures we stay focused and keep our project organized as we build out the app.
Cleaned Up Files
Here are the cleaned-up contents of the key files:
app/_layout.tsx
import { useFonts } from "expo-font";
import { Stack } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { StatusBar } from "expo-status-bar";
import { useEffect } from "react";
import { View } from "react-native";
import "react-native-reanimated";
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) {
return null;
}
return (
<View>
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
</Stack>
<StatusBar style="auto" />
</View>
);
}
app/index.tsx
import { View } from "react-native";
export default function HomeScreen() {
return <View></View>;
}
This streamlined setup ensures we stay focused and keep our project organized as we build out the app.
Step 3: Setting Up NativeWind
NativeWind is a library that brings the simplicity and power of Tailwind CSS to React Native. Tailwind CSS is a utility-first CSS framework that allows you to rapidly build modern and responsive designs by applying pre-defined utility classes directly to your components. NativeWind adapts this for React Native, enabling you to style your app with ease and consistency.
Configuration Steps
Follow the NativeWind Setup Guide: Use the NativeWind setup guide and select the Expo setup. Make sure to follow the latest version, as this tutorial is based on Expo SDK 52 at the time of writing.
-
Update the Tailwind Config File: Ensure your
tailwind.config.js
includes thecomponents
folder in addition to theapp
folder
/** @type {import('tailwindcss').Config} */ module.exports = { // NOTE: Update this to include the paths to all of your component files. content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"], presets: [require("nativewind/preset")], theme: { extend: {}, }, plugins: [], };
-
Create and Import the globals.css File: At the root of your project (same level as
package.json
), create a file namedglobals.css
. Copy the content from the NativeWind guide into this file. Then, import it at the top ofapp/_layout.tsx
:
import "../globals.css";
-
Generate and Edit babel.config.js: If your project doesn’t include a
babel.config.js
file (as Expo hides it by default), generate one by running:
npx expo customize
Select the
babel.config.js
option using the space bar and hit Enter. Once generated, copy the content from the NativeWind guide into this file. Generate and Edit metro.config.js: Similarly, if your project lacks a
metro.config.js
file, generate it using the same steps. Copy the content from the NativeWind guide into this file as well.-
Edit app.json: The NativeWind guide includes instructions for modifying the web section in the app.json file. However, since we are building for mobile only, you can safely remove the entire web section. Your app.json file should now look like this, with the rest of its content untouched:
{ "expo": { // other configurations here } }
-
TypeScript Configuration (Required): Create a file named
nativewind-env.d.ts
at the root of your project with the following content:
/// <reference types="nativewind/types" />
This step is required for proper type support in our TypeScript-based app.
Verification
Before running your app, ensure you’ve updated the screen files to the following:
app/_layout.tsx
<View className="flex-1">
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
</Stack>
<StatusBar style="auto" />
</View>
app/index.tsx
<View className="bg-red-500 flex-1">
<Text>Hello World</Text>
</View>
Once these steps are complete, start your app with the following command:
npx expo start
You should now see an ugly red screen with a Hello World
text in the top left corner (it will be behind the status bar as it’s outside the safe area), and a tab navigation bar at the bottom with a single tab. At this point, we’re almost done!
Step 4: Setting Up SafeAreaProvider and SafeAreaView
Lastly, we will incorporate the SafeAreaProvider
and SafeAreaView
components into our app, which are part of the react-native-safe-area-context
library. These components help ensure that your content is displayed within the safe area boundaries of a device, avoiding overlaps with the status bar, notch, or other UI elements.
- SafeAreaProvider: This component provides the context for safe area boundaries and should wrap your entire application.
- SafeAreaView: This component applies the safe area insets to its children. It can be used selectively for specific screens or layouts where you want to ensure the content is contained within the safe area.
For simplicity, in this tutorial, we’ll wrap the entire app with SafeAreaView
. Here's how to integrate it:
- Import
SafeAreaProvider
andSafeAreaView
intoapp/_layout.tsx
:
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
- Update the
app/_layout.tsx
file to wrap your app as follows:
<SafeAreaProvider>
<SafeAreaView className="flex-1">
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
</Stack>
</SafeAreaView>
<StatusBar style="auto" />
</SafeAreaProvider>
By wrapping the app with SafeAreaProvider
and SafeAreaView
, we ensure all screens respect the safe area. While it’s possible to use SafeAreaView
selectively for specific screens, we are keeping it simple by applying it globally in this tutorial.
Once you’ve made these updates, update your app or simply re-run it using:
npx expo start
You should now see the red screen with a Hello World
message, properly contained within the safe area and a tab navigation bar at the bottom.
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.
-
Install Replyke for Expo: Open your terminal and run the following command to install Replyke for your Expo project:
npx expo install replyke-expo
Note: The correct package for Expo-managed workflows is
replyke-expo
. You might see references toreplyke-rn
in some documentation—this package is intended for React Native CLI projects. Wrap Your App with
ReplykeProvider
: In yourapp/_layout
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.Add the
TokenManager
Component: Immediately inside theReplyke
, include theTokenManager
component. This component doesn’t need to wrap any other elements.
Here’s how the return statement of your app/_layout file should look after making these changes:
export default function Layout() {
return (
<ReplykeProvider projectId={process.env.EXPO_PUBLIC_REPLYKE_PROJECT_ID!}>
<TokenManaeger />
<SafeAreaProvider>
<SafeAreaView className="flex-1">
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="index" options={{ headerShown: false }} />
</Stack>
</SafeAreaView>
<StatusBar style="dark" />
</SafeAreaProvider>
</ReplykeProvider>
);
}
With these steps completed, Replyke is now set up and ready to power your comment section. Next, we’ll configure the signedToken
prop to authenticate our mock user and enable interaction with the comment system. Let’s move forward!
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" },
{ id: "user5", username: "daniel567" },
{ id: "user6", username: "chris88" },
{ id: "user7", username: "lucy999" },
{ id: "user8", username: "kate77" },
{ id: "user9", username: "noah321" },
{ id: "user10", username: "olivia654" },
];
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. In the next section, we’ll configure the
signedToken
prop and integrate the comment section with this data.
Render posts
Lastly, let's present our dummy posts in our home screen in a nice list. Replace the contents of app/index.tsx with the following:
import { Text, View, FlatList, TouchableOpacity } from "react-native";
import { posts } from "../constants/dummy-data";
export default function HomeScreen() {
return (
<View className="bg-gray-100 flex-1 p-4">
<FlatList
data={posts}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View className="bg-white p-4 mb-4 rounded-xl shadow-lg">
<Text className="text-lg font-bold text-gray-800">{item.id}</Text>
<Text className="text-gray-600 mt-2">{item.content}</Text>
<TouchableOpacity
className="bg-blue-500 mt-4 p-3 rounded-lg"
onPress={() => console.log(`Open comments for ${item.id}`)}
>
<Text className="text-white text-center font-medium">
Open Discussion
</Text>
</TouchableOpacity>
</View>
)}
/>
</View>
);
}
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: Implementing the Comment Section
Now that we have our project set up, it’s time to implement the comment section using @gorhom/bottom-sheet
. This library provides a smooth and modern UI for displaying content in a bottom sheet, which works perfectly for our comment section.
Installing Dependencies
To get started, install the required dependencies:
npx expo install @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler
Note: React Native Gesture Handler v3 and React Native Reanimated v3 require additional setup steps. Follow their respective installation guides:
Additionally, ensure your app is wrapped with GestureHandlerRootView
in app/_layout.tsx
as follows:
import { GestureHandlerRootView } from "react-native-gesture-handler";
export default function Layout() {
return (
<GestureHandlerRootView>
<ReplykeProvider projectId={process.env.EXPO_PUBLIC_REPLYKE_PROJECT_ID!}>
<TokenManager />
<SafeAreaProvider>
<SafeAreaView className="flex-1">
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="index" options={{ headerShown: false }} />
</Stack>
</SafeAreaView>
<StatusBar style="dark" />
</SafeAreaProvider>
</ReplykeProvider>
</GestureHandlerRootView>
);
}
Creating the Comment Section Component
Next, create a new file called CommentSectionSheet.tsx
inside your components
folder. This file will define the bottom sheet UI for the comment section. Add the following code:
import React, {
forwardRef,
useCallback,
useEffect,
useMemo,
useRef,
} from "react";
import { View, Text, Platform, KeyboardAvoidingView } from "react-native";
import {
useSocialComments,
useSocialStyle,
UseSocialStyleProps,
} from "replyke-expo";
import BottomSheet, {
BottomSheetBackdrop,
BottomSheetBackdropProps,
BottomSheetView,
} from "@gorhom/bottom-sheet";
const CommentSectionSheet = forwardRef<
BottomSheet,
{ referenceId: string | null; handleClose: () => void }
>(
(
{
referenceId,
handleClose,
}: { referenceId: string | null; handleClose: () => void },
ref
) => {
const snapPoints = useMemo(() => ["100%"], []);
const customStyles = useMemo<Partial<UseSocialStyleProps>>(
() => ({
commentProps: {},
newCommentFormProps: {
verticalPadding: 16,
paddingLeft: 24,
paddingRight: 24,
},
}),
[]
);
const styleConfig = useSocialStyle(customStyles);
const {
CommentSectionProvider,
CommentsFeed,
NewCommentForm,
SortByButton,
} = useSocialComments({
referenceId,
styleConfig,
createIfNotFound: true,
callbacks: {
loginRequiredCallback: () => {
alert(
"Oops! Login Required. Please sign in or create an account to continue."
);
},
},
});
const commentFormRef = useRef<{ focus: () => void } | null>(null);
useEffect(() => {
if (!referenceId) return;
const timeout = setTimeout(() => {
if (commentFormRef.current) {
commentFormRef.current.focus();
}
}, 1000);
return () => clearTimeout(timeout);
}, [referenceId]);
const renderBackdrop = useCallback(
(props: BottomSheetBackdropProps) => (
<BottomSheetBackdrop
{...props}
disappearsOnIndex={-1}
appearsOnIndex={0}
/>
),
[]
);
return (
<BottomSheet
index={-1}
ref={ref} // Forwarding the ref here
snapPoints={snapPoints}
enablePanDownToClose
backdropComponent={renderBackdrop}
onClose={handleClose}
>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
className="flex-1"
>
<CommentSectionProvider>
<BottomSheetView className="relative flex-1 h-full">
<View className="flex-row gap-2 px-4 items-center mb-2">
<View className="flex-1" />
<SortByButton
priority="top"
activeView={
<Text className="bg-black py-2 px-3 rounded-md text-white text-sm">
Top
</Text>
}
nonActiveView={
<Text className="bg-gray-200 py-2 px-3 rounded-md text-sm">
Top
</Text>
}
/>
<SortByButton
priority="new"
activeView={
<Text className="bg-black py-2 px-3 rounded-md text-white text-sm">
New
</Text>
}
nonActiveView={
<Text className="bg-gray-200 py-2 px-3 rounded-md text-sm">
New
</Text>
}
/>
</View>
<CommentsFeed />
{/* <View className="border-t-hairline border-gray-300"> */}
<NewCommentForm ref={commentFormRef} />
{/* </View> */}
</BottomSheetView>
</CommentSectionProvider>
</KeyboardAvoidingView>
</BottomSheet>
);
}
);
export default CommentSectionSheet;
Explanation of the CommentSectionSheet
Component
-
useSocialComments
andreferenceId
:
The
useSocialComments
hook is configured to use thereferenceId
passed as a prop. ThisreferenceId
links the comments in Replyke with external data, like the posts from our dummy array.When using external data, the
createIfNotFound
flag ensures that Replyke creates a new entity for comments if it doesn’t already exist.
Style Configuration:
TheuseSocialStyle
hook provides a style configuration object for customizing the comment section’s appearance. We pass custom padding for the new comment form here.Login Required Callback:
TheloginRequiredCallback
function alerts users to log in if they try to perform actions restricted to authenticated users.commentFormRef
anduseEffect
:
ThecommentFormRef
is used to programmatically focus on the comment input field when the bottom sheet opens. A slight delay is introduced usingsetTimeout
for better user experience.Sort Buttons:
The twoSortByButton
components allow users to toggle between sorting comments by “Top” and “New.” Active and inactive states are styled differently for clarity.Bottom Sheet Control:
Theref
passed to the bottom sheet allows us to programmatically control its behavior from the parent component.
Updating the Home Screen
Finally, update your HomeScreen
to include the comment section. Replace its content with the following:
import { useRef, useState } from "react";
import { Text, View, FlatList, TouchableOpacity } from "react-native";
import BottomSheet from "@gorhom/bottom-sheet";
import { posts } from "../constants/dummy-data";
import CommentSectionSheet from "../components/CommentSectionSheet";
export default function HomeScreen() {
const bottomSheetRef = useRef<BottomSheet>(null);
const [referenceId, setReferenceId] = useState<string | null>(null);
const handleOpen = (newReferenceId: string) => {
if (bottomSheetRef.current) {
setReferenceId(newReferenceId);
bottomSheetRef.current.expand(); // `expand` method now has TypeScript autocomplete
}
};
const handleClose = () => {
if (bottomSheetRef.current) {
bottomSheetRef.current.close();
setReferenceId(null);
}
};
return (
<View className="bg-gray-100 flex-1 p-4">
<FlatList
data={posts}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View className="bg-white p-4 mb-4 rounded-xl shadow-lg">
<Text className="text-lg font-bold text-gray-800">{item.id}</Text>
<Text className="text-gray-600 mt-2">{item.content}</Text>
<TouchableOpacity
className="bg-blue-500 mt-4 p-3 rounded-lg"
onPress={() => handleOpen(item.id)}
>
<Text className="text-white text-center font-medium">
Open Discussion
</Text>
</TouchableOpacity>
</View>
)}
/>
<CommentSectionSheet
ref={bottomSheetRef}
referenceId={referenceId}
handleClose={handleClose}
/>
</View>
);
}
With this setup, clicking the "Open Discussion" button on any card will open the corresponding comment section in a bottom sheet. You’ve now successfully implemented a modern, fully-featured comment section in your app!
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 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 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 app's
_layout.tsx
file for authentication setup.
Updated app/_layout.tsx
import "../global.css";
import { useFonts } from "expo-font";
import { Stack } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { StatusBar } from "expo-status-bar";
import { useEffect, useState } from "react";
import "react-native-reanimated";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { ReplykeProvider, TokenManager, useSignTestingJwt } from "replyke-expo";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { users } from "../constants/dummy-data";
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
const projectId = process.env.EXPO_PUBLIC_REPLYKE_PROJECT_ID!;
const privateKey = process.env.EXPO_PUBLIC_REPLYKE_SECRET_KEY!;
export default function RootLayout() {
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
const signTestingJwt = useSignTestingJwt();
const [signedToken, setSignedToken] = useState<string>();
const handleSignJwt = async () => {
const payload = users[0]; // Using the first user from dummy data
const token = await signTestingJwt({
projectId,
payload,
privateKey,
});
setSignedToken(token);
};
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
useEffect(() => {
handleSignJwt();
}, []);
if (!loaded) {
return null;
}
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<ReplykeProvider projectId={projectId} signedToken={signedToken}>
<TokenManager />
<SafeAreaProvider>
<SafeAreaView className="flex-1">
<Stack
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="index" options={{ headerShown: false }} />
</Stack>
</SafeAreaView>
<StatusBar style="dark" />
</SafeAreaProvider>
</ReplykeProvider>
</GestureHandlerRootView>
);
}
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-expo";
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.
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 Articles: Implement a simple and efficient like functionality for articles, 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 Articles
Implementing likes for articles is just as straightforward. Here's how:
- Wrap Each Post with an
EntityProvider
: Update theFlatList
in yourHomeScreen
to wrap each post with anEntityProvider
.
<FlatList
data={posts}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<EntityProvider referenceId={item.id} createIfNotFound key={item.id}>
<SinglePost item={item} handleOpen={handleOpen} />
</EntityProvider>
)}
/>
- Create a
SinglePost
Component: Create a newSinglePost
component in yourcomponents
directory.
import { View, Text, TouchableOpacity } from "react-native";
import React from "react";
import { useEntity } from "replyke-expo";
const SinglePost = ({
item,
handleOpen,
}: {
item: { id: string; content: string };
handleOpen: (newReferenceId: string) => void;
}) => {
const {
entity,
userDownvotedEntity,
userUpvotedEntity,
upvoteEntity,
removeEntityUpvote,
downvoteEntity,
removeEntityDownvote,
} = useEntity();
const upvotesCount = entity?.upvotes.length || 0;
const downvotesCount = entity?.downvotes.length || 0;
const handleUpvote = () => {
if (userUpvotedEntity) {
removeEntityUpvote?.();
} else {
upvoteEntity?.();
}
};
const handleDownvote = () => {
if (userDownvotedEntity) {
removeEntityDownvote?.();
} else {
downvoteEntity?.();
}
};
return (
<View className="bg-white p-4 mb-4 rounded-xl shadow-lg">
<Text className="text-lg font-bold text-gray-800">{item.id}</Text>
<Text className="text-gray-600 mt-2">{item.content}</Text>
<View className="flex-row justify-between items-center mt-4">
<TouchableOpacity
className={`flex-row items-center p-3 rounded-lg ${
userUpvotedEntity ? "bg-green-500" : "bg-gray-200"
}`}
onPress={handleUpvote}
>
<Text
className={`font-medium ${
userUpvotedEntity ? "text-white" : "text-gray-800"
}`}
>
{userUpvotedEntity ? "Upvoted" : "Upvote"}
</Text>
<Text className="ml-2 text-sm text-gray-600">({upvotesCount})</Text>
</TouchableOpacity>
<TouchableOpacity
className={`flex-row items-center p-3 rounded-lg ${
userDownvotedEntity ? "bg-red-500" : "bg-gray-200"
}`}
onPress={handleDownvote}
>
<Text
className={`font-medium ${
userDownvotedEntity ? "text-white" : "text-gray-800"
}`}
>
{userDownvotedEntity ? "Downvoted" : "Downvote"}
</Text>
<Text className="ml-2 text-sm text-gray-600">({downvotesCount})</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
className="bg-blue-500 mt-4 p-3 rounded-lg"
onPress={() => handleOpen(item.id)}
>
<Text className="text-white text-center font-medium">
Open Discussion
</Text>
</TouchableOpacity>
</View>
);
};
export default SinglePost;
That's it! Your articles 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 articles. 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)