https://github.com/harshsahuu1234/reactnative_restateapp
Built a full-stack Real Estate app from scratch with Google Authentication, dynamic routing, and more. Mastered essential skills for scalable and clean React Native development.
https://github.com/harshsahuu1234/reactnative_restateapp
appwrite appwrite-auth nativewind react-native typescript
Last synced: 8 months ago
JSON representation
Built a full-stack Real Estate app from scratch with Google Authentication, dynamic routing, and more. Mastered essential skills for scalable and clean React Native development.
- Host: GitHub
- URL: https://github.com/harshsahuu1234/reactnative_restateapp
- Owner: HarshSahuu1234
- Created: 2025-08-28T15:42:13.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2025-08-28T16:11:52.000Z (8 months ago)
- Last Synced: 2025-08-28T22:46:00.747Z (8 months ago)
- Topics: appwrite, appwrite-auth, nativewind, react-native, typescript
- Language: TypeScript
- Homepage:
- Size: 199 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
A Real Estate App
## đź“‹ Table of Contents
1. 🤖 [Introduction](#introduction)
2. ⚙️ [Tech Stack](#tech-stack)
3. 🔋 [Features](#features)
4. 🕸️ [Snippets](#snippets)
5. đź”— [Assets](#links)
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.
- **[Expo](https://expo.dev/)** 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](https://reactnative.dev/)** 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](https://jsm.dev/rn25-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](https://www.typescriptlang.org/)** 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](https://www.nativewind.dev/)** 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](https://tailwindcss.com/)** 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
**Prerequisites**
Make sure you have the following installed on your machine:
- [Git](https://git-scm.com/)
- [Node.js](https://nodejs.org/en)
- [npm](https://www.npmjs.com/) (Node Package Manager)
**Cloning the Repository**
```bash
git clone https://github.com/adrianhajdin/react_native-restate.git
cd react_native-restate
```
**Installation**
```bash
npm install
```
**Set Up Environment Variables**
Create a new file named `.env.local` in the root of your project and add the following content:
```env
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**](https://jsm.dev/rn25-appwrite).
**Start the app**
```bash
npx expo start
```
In the output, you'll find options to open the app in a
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/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](https://docs.expo.dev/router/introduction).
lib/data.ts
```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
```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(
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
```ts
import { Alert } from "react-native";
import { useEffect, useState, useCallback } from "react";
interface UseAppwriteOptions> {
fn: (params: P) => Promise;
params?: P;
skip?: boolean;
}
interface UseAppwriteReturn {
data: T | null;
loading: boolean;
error: string | null;
refetch: (newParams: P) => Promise;
}
export const useAppwrite = >({
fn,
params = {} as P,
skip = false,
}: UseAppwriteOptions): UseAppwriteReturn => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(!skip);
const [error, setError] = useState(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
Assets and snippets used in the project can be found in the **[video kit](https://jsm.dev/rn25-restate)**.
Appwrite Database Setup can be found [here](https://jsmastery.notion.site/Database-Setup-16260f3cbaf3807f8fb6cbed8d1e84fd)