Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/adrianhajdin/react_native-restate
Build a full-stack Real Estate app from scratch with Google Authentication, dynamic routing, and more. Master essential skills for scalable and clean React Native development.
https://github.com/adrianhajdin/react_native-restate
expo react-native
Last synced: 2 days ago
JSON representation
Build a full-stack Real Estate app from scratch with Google Authentication, dynamic routing, and more. Master essential skills for scalable and clean React Native development.
- Host: GitHub
- URL: https://github.com/adrianhajdin/react_native-restate
- Owner: adrianhajdin
- Created: 2024-12-19T10:54:00.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2024-12-21T10:12:57.000Z (about 2 months ago)
- Last Synced: 2025-02-13T03:49:25.905Z (3 days ago)
- Topics: expo, react-native
- Language: TypeScript
- Homepage: https://jsmastery.pro
- Size: 5.54 MB
- Stars: 375
- Watchers: 3
- Forks: 80
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
![]()
![]()
![]()
![]()
![]()
A Real Estate App
Build this project step by step with our detailed tutorial on JavaScript Mastery YouTube. Join the JSM family!
## π Table of Contents
1. π€ [Introduction](#introduction)
2. βοΈ [Tech Stack](#tech-stack)
3. π [Features](#features)
4. π€Έ [Quick Start](#quick-start)
5. πΈοΈ [Snippets](#snippets)
6. π [Assets](#links)
7. π [More](#more)## π¨ Tutorial
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
- React Native
- TypeScript
- Nativewind
- Appwrite
- Tailwind CSSπ **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:
- [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 website](https://apwr.dev/JSM050).
**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 ExpoYou 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 galleriesconst 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 Constants used in the project can be found [here](https://drive.google.com/file/d/1HxuvAeJfiPfcZ1co5fU0ahKRw4sbA0gO/view?usp=sharing)
Appwrite Database Setup can be found [here](https://jsmastery.notion.site/Database-Setup-16260f3cbaf3807f8fb6cbed8d1e84fd)
## π More
**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!