
π Table of Contents
- π€ Introduction
- βοΈ Tech Stack
- π Features
- π€Έ Quick Start
- πΈοΈ Snippets
- π Assets
- π More
This repository contains the code corresponding to an in-depth tutorial available on our YouTube channel, JavaScript Mastery.
If you prefer visual learning, this is the perfect resource for you. Follow our tutorial to learn how to build projects like these step-by-step in a beginner-friendly manner!
Build a full-stack Real Estate application with React Native, featuring Google authentication, dynamic property listings, and user profiles. Designed with modern tools like Expo SDK 52, Appwrite, Tailwind CSS, and TypeScript for a seamless and scalable experience.
If you're getting started and need assistance or face any bugs, join our active Discord community with over 50k+ members. It's a place where people help each other out.
-
Expo is an open-source platform for building universal native apps (Android, iOS, web) using JavaScript/TypeScript and React Native. It features file-based routing via Expo Router, fast refresh, native modules for camera/maps/notifications, over-the-air updates (EAS), and streamlined app deployment.
-
React Native is a framework for building mobile UIs with React. It enables componentβbased, cross-platform development with declarative UI, deep native API support, and is tightly integrated with Expo for navigation and native capabilities.
-
Appwrite is an open-source backend-as-a-service platform offering secure authentication (email/password, OAuth, SMS, magic links), databases, file storage with compression/encryption, real-time messaging, serverless functions, and static site hosting via Appwrite Sitesβall managed through a unified console and microservices architecture.
-
TypeScript is a statically-typed superset of JavaScript providing type annotations, interfaces, enums, generics, and enhanced tooling. It improves error detection, code quality, and scalabilityβideal for robust, maintainable projects.
-
NativeWind brings Tailwind CSS to React Native and Expo, allowing you to style mobile components using utility-first classes for fast, consistent, and responsive UI design.
-
Tailwind CSS is a utility-first CSS framework enabling rapid UI design via low-level classes. In React Native/Expo, itβs commonly used with NativeWind to apply Tailwind-style utilities to mobile components.
π Authentication with Google: Secure and seamless user sign-ins using Googleβs authentication service.
π Home Page: Displays the latest and recommended properties with powerful search and filter functionality.
π Explore Page: Allows users to browse all types of properties with a clean and intuitive interface.
π Property Details Page: Provides comprehensive information about individual properties, including images and key details.
π Profile Page: Customizable user settings and profile management
π Centralized Data Fetching: Custom-built solution inspired by TanStackβs useQuery for efficient API calls.
and many more, including code architecture and reusability
Follow these steps to set up the project locally on your machine.
Prerequisites
Make sure you have the following installed on your machine:
Cloning the Repository
git clone https://github.com/adrianhajdin/react_native-restate.git
cd react_native-restate
Installation
npm install
Set Up Environment Variables
Create a new file named .env.local
in the root of your project and add the following content:
EXPO_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
EXPO_PUBLIC_APPWRITE_PROJECT_ID=
EXPO_PUBLIC_APPWRITE_DATABASE_ID=
EXPO_PUBLIC_APPWRITE_GALLERIES_COLLECTION_ID=
EXPO_PUBLIC_APPWRITE_REVIEWS_COLLECTION_ID=
EXPO_PUBLIC_APPWRITE_AGENTS_COLLECTION_ID=
EXPO_PUBLIC_APPWRITE_PROPERTIES_COLLECTION_ID=
Replace the values with your actual Appwrite credentials. You can obtain these credentials by signing up & creating a new project on the Appwrite Dashboard.
Start the app
npx expo start
In the output, you'll find options to open the app in a
- development build
- Android emulator
- iOS simulator
- Expo Go, a limited sandbox for trying out app development with Expo
You can start developing by editing the files inside the app directory. This project uses file-based routing.
lib/data.ts
export const galleryImages = [
"https://images.unsplash.com/photo-1507089947368-19c1da9775ae?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://unsplash.com/photos/comfort-room-with-white-bathtub-and-brown-wooden-cabinets-CMejBwGAdGk",
"https://images.unsplash.com/photo-1638799869566-b17fa794c4de?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1560185009-dddeb820c7b7?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1641910532059-ad684fd3049c?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1621293954908-907159247fc8?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1604328702728-d26d2062c20b?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1600435335786-d74d2bb6de37?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1560448204-603b3fc33ddc?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1635108198979-9806fdf275c6?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];
export const agentImages = [
"https://images.unsplash.com/photo-1691335053879-02096d6ee2ca?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1544723495-432537d12f6c?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1492562080023-ab3db95bfbce?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1542507464418-09c375b86bbe?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1534308143481-c55f00be8bd7?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];
export const reviewImages = [
"https://images.unsplash.com/photo-1517331671191-ddc2c6d3ebd1?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1474176857210-7287d38d27c6?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1511551203524-9a24350a5771?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1507591064344-4c6ce005b128?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];
export const propertiesImages = [
"https://images.unsplash.com/photo-1580587771525-78b9dba3b914?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1605146768851-eda79da39897?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1568605114967-8130f3a36994?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1564013799919-ab600027ffc6?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1561753757-d8880c5a3551?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1551241090-67de81d3541c?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1697299262049-e9b5fa1e9761?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1719299225324-301bad5c333c?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1582063289852-62e3ba2747f8?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1516095901529-0ef7be431a4f?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1600585153490-76fb20a32601?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1605276373954-0c4a0dac5b12?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1583608205776-bfd35f0d9f83?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1720432972486-2d53db5badf0?q=60&w=640&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];
lib/seed.ts
import { ID } from "react-native-appwrite";
import { databases, config } from "./appwrite";
import {
agentImages,
galleryImages,
propertiesImages,
reviewImages,
} from "./data";
const COLLECTIONS = {
AGENT: config.agentsCollectionId,
REVIEWS: config.reviewsCollectionId,
GALLERY: config.galleriesCollectionId,
PROPERTY: config.propertiesCollectionId,
};
const propertyTypes = [
"House",
"Townhomes",
"Condos",
"Duplexes",
"Studios",
"Villa",
"Apartments",
"Others",
];
const facilities = [
"Laundry",
"Car Parking",
"Sports Center",
"Cutlery",
"Gym",
"Swimming pool",
"Wifi",
"Pet Center",
];
function getRandomSubset<T>(
array: T[],
minItems: number,
maxItems: number
): T[] {
if (minItems > maxItems) {
throw new Error("minItems cannot be greater than maxItems");
}
if (minItems < 0 || maxItems > array.length) {
throw new Error(
"minItems or maxItems are out of valid range for the array"
);
}
// Generate a random size for the subset within the range [minItems, maxItems]
const subsetSize =
Math.floor(Math.random() * (maxItems - minItems + 1)) + minItems;
// Create a copy of the array to avoid modifying the original
const arrayCopy = [...array];
// Shuffle the array copy using Fisher-Yates algorithm
for (let i = arrayCopy.length - 1; i > 0; i--) {
const randomIndex = Math.floor(Math.random() * (i + 1));
[arrayCopy[i], arrayCopy[randomIndex]] = [
arrayCopy[randomIndex],
arrayCopy[i],
];
}
// Return the first `subsetSize` elements of the shuffled array
return arrayCopy.slice(0, subsetSize);
}
async function seed() {
try {
// Clear existing data from all collections
for (const key in COLLECTIONS) {
const collectionId = COLLECTIONS[key as keyof typeof COLLECTIONS];
const documents = await databases.listDocuments(
config.databaseId!,
collectionId!
);
for (const doc of documents.documents) {
await databases.deleteDocument(
config.databaseId!,
collectionId!,
doc.$id
);
}
}
console.log("Cleared all existing data.");
// Seed Agents
const agents = [];
for (let i = 1; i <= 5; i++) {
const agent = await databases.createDocument(
config.databaseId!,
COLLECTIONS.AGENT!,
ID.unique(),
{
name: `Agent ${i}`,
email: `agent${i}@example.com`,
avatar: agentImages[Math.floor(Math.random() * agentImages.length)],
}
);
agents.push(agent);
}
console.log(`Seeded ${agents.length} agents.`);
// Seed Reviews
const reviews = [];
for (let i = 1; i <= 20; i++) {
const review = await databases.createDocument(
config.databaseId!,
COLLECTIONS.REVIEWS!,
ID.unique(),
{
name: `Reviewer ${i}`,
avatar: reviewImages[Math.floor(Math.random() * reviewImages.length)],
review: `This is a review by Reviewer ${i}.`,
rating: Math.floor(Math.random() * 5) + 1, // Rating between 1 and 5
}
);
reviews.push(review);
}
console.log(`Seeded ${reviews.length} reviews.`);
// Seed Galleries
const galleries = [];
for (const image of galleryImages) {
const gallery = await databases.createDocument(
config.databaseId!,
COLLECTIONS.GALLERY!,
ID.unique(),
{ image }
);
galleries.push(gallery);
}
console.log(`Seeded ${galleries.length} galleries.`);
// Seed Properties
for (let i = 1; i <= 20; i++) {
const assignedAgent = agents[Math.floor(Math.random() * agents.length)];
const assignedReviews = getRandomSubset(reviews, 5, 7); // 5 to 7 reviews
const assignedGalleries = getRandomSubset(galleries, 3, 8); // 3 to 8 galleries
const selectedFacilities = facilities
.sort(() => 0.5 - Math.random())
.slice(0, Math.floor(Math.random() * facilities.length) + 1);
const image =
propertiesImages.length - 1 >= i
? propertiesImages[i]
: propertiesImages[
Math.floor(Math.random() * propertiesImages.length)
];
const property = await databases.createDocument(
config.databaseId!,
COLLECTIONS.PROPERTY!,
ID.unique(),
{
name: `Property ${i}`,
type: propertyTypes[Math.floor(Math.random() * propertyTypes.length)],
description: `This is the description for Property ${i}.`,
address: `123 Property Street, City ${i}`,
geolocation: `192.168.1.${i}, 192.168.1.${i}`,
price: Math.floor(Math.random() * 9000) + 1000,
area: Math.floor(Math.random() * 3000) + 500,
bedrooms: Math.floor(Math.random() * 5) + 1,
bathrooms: Math.floor(Math.random() * 5) + 1,
rating: Math.floor(Math.random() * 5) + 1,
facilities: selectedFacilities,
image: image,
agent: assignedAgent.$id,
reviews: assignedReviews.map((review) => review.$id),
gallery: assignedGalleries.map((gallery) => gallery.$id),
}
);
console.log(`Seeded property: ${property.name}`);
}
console.log("Data seeding completed.");
} catch (error) {
console.error("Error seeding data:", error);
}
}
export default seed;
lib/useAppwrite.ts
import { Alert } from "react-native";
import { useEffect, useState, useCallback } from "react";
interface UseAppwriteOptions<T, P extends Record<string, string | number>> {
fn: (params: P) => Promise<T>;
params?: P;
skip?: boolean;
}
interface UseAppwriteReturn<T, P> {
data: T | null;
loading: boolean;
error: string | null;
refetch: (newParams: P) => Promise<void>;
}
export const useAppwrite = <T, P extends Record<string, string | number>>({
fn,
params = {} as P,
skip = false,
}: UseAppwriteOptions<T, P>): UseAppwriteReturn<T, P> => {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(!skip);
const [error, setError] = useState<string | null>(null);
const fetchData = useCallback(
async (fetchParams: P) => {
setLoading(true);
setError(null);
try {
const result = await fn(fetchParams);
setData(result);
} catch (err: unknown) {
const errorMessage =
err instanceof Error ? err.message : "An unknown error occurred";
setError(errorMessage);
Alert.alert("Error", errorMessage);
} finally {
setLoading(false);
}
},
[fn]
);
useEffect(() => {
if (!skip) {
fetchData(params);
}
}, []);
const refetch = async (newParams: P) => await fetchData(newParams);
return { data, loading, error, refetch };
};
Assets and snippets used in the project can be found in the video kit.
Appwrite Database Setup can be found here

Advance your skills with Next.js Pro Course
Enjoyed creating this project? Dive deeper into our PRO courses for a richer learning adventure. They're packed with
detailed explanations, cool features, and exercises to boost your skills. Give it a go!
