Skip to content

Commit 4d0bc8d

Browse files
authored
Merge pull request #11 from expo-starter/chore/upgrade-drizzleorm
chore: upgrade drizzle ORM and add test live query
2 parents 8a693d1 + 6a69811 commit 4d0bc8d

14 files changed

+87
-181
lines changed

.vscode/extension.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"recommendations": [
3+
"biomejs.biome",
4+
"expo.vscode-expo-tools",
5+
"bradlc.vscode-tailwindcss"
6+
]
7+
}

README.md

+3-4
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,17 @@ Say goodbye to time-consuming chores such implementing libraries, and developing
1616
- 💎 Integrate with [NativeWind v4](https://www.nativewind.dev), Tailwind CSS for React Native
1717
- 📁 Expo Router and Expo API
1818
- 📦 [zustand](docs.pmnd.rs/zustand) - State management solution.
19-
- 🎨 Common components : Icons, ThemeToggle, Avatar, Button, Card, Progress, Text, Tooltip
19+
- 🎨 Common components from the [rn-reusables](https://github.com/mrzachnugent/react-native-reusables) library: Icons, ThemeToggle, Avatar, Button, Card, Progress, Text, Tooltip.
2020
- 🌗 Dark and light mode - Android Navigation Bar matches mode and Persistant mode
2121
- 💡 Absolute Imports using `@` prefix
2222
- 📏 Linter and Code Formatter with [biome](https://biomejs.dev/)
2323
- 🗂 VSCode recommended extensions, settings, and snippets to enhance the developer experience.
2424

2525
New :
2626
- 💽 Local-first based on [Expo SQLite for](https://docs.expo.dev/versions/latest/sdk/sqlite/) for native and [Sqlite.js](https://github.com/sql-js/sql.js) for Web
27-
- 💽 Full integrated with [DrizzleORM](https://drizzle.dev)
27+
- 💽 Full integrated with [DrizzleORM](https://drizzle.dev) including live query
2828

2929
In-progress :
30-
- Live query with [DrizzleORM](https://drizzle.dev)
3130
- [Cloudflare D1](https://developers.cloudflare.com/d1/) for data persistance on Web as well as offline mode
3231

3332
### Requirements
@@ -70,5 +69,5 @@ See [LICENSE](LICENSE) for more information.
7069

7170
---
7271

73-
Made with ♥ by [Launchtrack](https://launchtrack.dev) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Follow%20%40younes200)](https://twitter.com/younes200)
72+
Made with ♥ by [Expo starter](expostarter.com) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Follow%20%40younes200)](https://twitter.com/younes200)
7473

app/_layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export default function RootLayout() {
7979
<BottomSheetModalProvider>
8080
<Stack initialRouteName="index" >
8181
<Stack.Screen name="index" />
82-
<Stack.Screen name="create" options={{presentation: "modal"}} />
82+
<Stack.Screen name="create" options={{presentation: "containedModal"}} />
8383

8484
<Stack.Screen
8585
name="settings/index"

app/create.tsx

+2-4
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,16 @@ export default function FormScreen() {
102102
async function handleSubmit(values: z.infer<typeof formSchema>) {
103103

104104
try {
105-
const id = await db?.insert(habitTable).values({
105+
await db?.insert(habitTable).values({
106106
...values,
107107
category: values.category.value,
108108
duration: Number(values.duration),
109109
}).returning()
110-
console.log("id", id)
110+
router.replace("/")
111111
} catch (e) {
112112
console.error(e)
113113
}
114114

115-
116-
router.dismiss()
117115
}
118116
return (
119117
<ScrollView

app/habits/[id].tsx

-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ import {Text} from "@/components/ui/text";
4949
import {useDatabase} from "@/db/provider";
5050
import {habitTable} from "@/db/schema";
5151
import {cn} from "@/lib/utils";
52-
import {ChevronLeft} from "lucide-react-native";
5352
import type {Habit} from "@/lib/storage";
5453

5554
const HabitCategories = [
@@ -189,7 +188,6 @@ export default function FormScreen() {
189188
<Stack.Screen
190189
options={{
191190
title: "Habit",
192-
headerLeft: () => <Pressable onPress={() => router.replace("/")}><ChevronLeft width={30} height={30} /></Pressable>
193191
}}
194192
/>
195193
<FormElement

app/index.tsx

+25-60
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,22 @@ import {FlashList} from "@shopify/flash-list";
33
import {eq} from "drizzle-orm";
44
import {Link, Stack, useFocusEffect, useRouter} from "expo-router";
55
import * as React from "react";
6-
import {FlatList, Pressable, View} from "react-native";
6+
import {Pressable, View} from "react-native";
77
import {ThemeToggle} from "@/components/ThemeToggle";
8-
import {Badge} from "@/components/ui/badge";
98
import {Button} from "@/components/ui/button";
9+
import {useLiveQuery} from "drizzle-orm/expo-sqlite";
1010

1111
import {Progress} from "@/components/ui/progress";
1212
import {Text} from "@/components/ui/text";
13-
import {type Habit, habitTable} from "@/db/schema";
13+
import {habitTable} from "@/db/schema";
1414
import {Plus, PlusCircle} from "@/components/Icons";
1515
import {useMigrationHelper} from "@/db/drizzle";
1616
import {useDatabase} from "@/db/provider";
17-
import Settings from "./settings";
1817
import {SettingsIcon} from "lucide-react-native";
1918

20-
import {useForm} from "react-hook-form";
21-
import {zodResolver} from "@hookform/resolvers/zod";
22-
import * as z from "zod";
23-
import {useSafeAreaInsets} from "react-native-safe-area-context";
24-
import {HabitCard} from "@/components/habit";
25-
2619

20+
import {HabitCard} from "@/components/habit";
21+
import type {Habit} from "@/lib/storage";
2722

2823
export default function Screen() {
2924
const {success, error} = useMigrationHelper();
@@ -45,67 +40,40 @@ export default function Screen() {
4540

4641
return <ScreenContent />;
4742
}
48-
const HabitCategories = [
49-
{value: "health", label: "Health And Wellness"},
50-
{value: "personal-development", label: "Personal Development"},
51-
{value: "social-and-relationshipts", label: "Social And Relationships"},
52-
{value: "productivity", label: "Productivity"},
53-
{value: "creativity", label: "Creativity"},
54-
{value: "mindfulness", label: "Mindfulness"},
55-
{value: "financial", label: "Financial"},
56-
{value: "leisure", label: "Leisure"},
57-
]
43+
5844
function ScreenContent() {
5945
const {db} = useDatabase();
46+
const {data: habits, error} = useLiveQuery(db?.select().from(habitTable));
6047

61-
const [habits, setHabits] = React.useState<Habit[]>([]);
6248
const ref = React.useRef(null);
6349
useScrollToTop(ref);
6450

6551
const router = useRouter();
6652

67-
useFocusEffect(
68-
React.useCallback(() => {
69-
fetchHabits();
70-
}, []),
53+
const renderItem = React.useCallback(
54+
({item}: {item: Habit}) => <HabitCard {...item} />,
55+
[],
7156
);
72-
const formSchema = z.object({
73-
category: z.object(
74-
{value: z.string(), label: z.string()},
75-
{
76-
invalid_type_error: 'Please select a category',
77-
}
78-
),
79-
});
80-
const form = useForm<z.infer<typeof formSchema>>({
81-
resolver: zodResolver(formSchema),
82-
values: {
83-
category: {value: "health", label: "Health And Wellness"}
84-
}
85-
}
86-
)
87-
const fetchHabits = async () => {
88-
const fetchedHabits = await db?.select().from(habitTable).execute();
89-
setHabits(fetchedHabits ?? []);
90-
};
91-
92-
const handleDeleteHabit = async (id: string) => {
93-
await db?.delete(habitTable).where(eq(habitTable.id, id)).execute();
94-
await fetchHabits();
95-
return;
96-
};
97-
98-
React.useEffect(() => {
99-
fetchHabits();
100-
}, []);
10157

58+
if (error) {
59+
return (
60+
<View className="flex-1 items-center justify-center bg-secondary/30">
61+
<Text className="text-destructive pb-2 ">Error Loading data</Text>
62+
</View>
63+
);
64+
}
10265
return (
10366
<View className="flex-1 gap-5 p-6 bg-secondary/30">
67+
10468
<Stack.Screen
10569
options={{
10670
title: "Habits",
10771
headerRight: () => <ThemeToggle />,
108-
headerLeft: () => <Button variant="link" onPress={() => router.navigate("settings")}><SettingsIcon /></Button>
72+
headerLeft: () => (
73+
<Button variant="link" onPress={() => router.navigate("settings")}>
74+
<SettingsIcon />
75+
</Button>
76+
),
10977
}}
11078
/>
11179
<FlashList
@@ -129,10 +97,8 @@ function ScreenContent() {
12997
)}
13098
ItemSeparatorComponent={() => <View className="p-2" />}
13199
data={habits}
132-
renderItem={({item}) => (
133-
<HabitCard habit={item} onPress={() => router.replace(`/habits/${ item.id }`)} />
134-
)}
135-
keyExtractor={(item) => item.id}
100+
renderItem={renderItem}
101+
keyExtractor={(_, index) => `item-${ index }`}
136102
ListFooterComponent={<View className="py-4" />}
137103
/>
138104
<View className="absolute bottom-10 right-8">
@@ -144,7 +110,6 @@ function ScreenContent() {
144110
</Pressable>
145111
</Link>
146112
</View>
147-
148113
</View>
149114
);
150115
}

bun.lockb

100644100755
1.79 KB
Binary file not shown.

components/habit/card.tsx

+29-29
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,37 @@ import {
1313
} from "@/components/ui/card";
1414
import {Badge} from "../ui/badge";
1515
import {Progress} from "../ui/progress";
16-
type HabitProps = {
17-
habit: Habit;
18-
onPress: () => void;
19-
};
16+
import {Link} from "expo-router";
2017

21-
export const HabitCard: React.FC<HabitProps> = ({habit, onPress}) => {
22-
return (
23-
<Pressable onPress={onPress}>
24-
<Card className="rounded-2xl">
25-
<CardHeader>
26-
<View className="flex-row gap-4 items-center">
27-
<CardTitle className="pb-2">
28-
{habit.name}
29-
</CardTitle>
30-
<Badge variant="outline">
31-
<Text >{habit.category}</Text>
32-
</Badge>
33-
</View>
18+
type HabitProps = Habit;
3419

35-
<View className="flex-col">
36-
<CardDescription className="text-base font-semibold">
37-
{habit.description}
38-
</CardDescription>
39-
</View>
40-
</CardHeader>
41-
<CardContent />
42-
<CardFooter className="flex-col gap-3 flex-1">
43-
<Progress value={10} className="h-2" indicatorClassName="bg-sky-600" />
44-
</CardFooter>
45-
</Card>
46-
</Pressable >
20+
export const HabitCard: React.FC<HabitProps> = ({id, name, description, category}: HabitProps) => {
21+
return (
22+
<Link href={`/habits/${ id }`} asChild>
23+
<Pressable>
24+
<Card className="rounded-2xl">
25+
<CardHeader>
26+
<View className="flex-row gap-4 items-center">
27+
<CardTitle className="pb-2">
28+
{name}
29+
</CardTitle>
30+
<Badge variant="outline">
31+
<Text >{category}</Text>
32+
</Badge>
33+
</View>
4734

35+
<View className="flex-col">
36+
<CardDescription className="text-base font-semibold">
37+
{description}
38+
</CardDescription>
39+
</View>
40+
</CardHeader>
41+
<CardContent />
42+
<CardFooter className="flex-col gap-3 flex-1">
43+
<Progress value={10} className="h-2" indicatorClassName="bg-sky-600" />
44+
</CardFooter>
45+
</Card>
46+
</Pressable>
47+
</Link>
4848
);
4949
};

components/habit/form.tsx

-64
This file was deleted.

components/habit/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export * from "./card";
2-
export * from "./form";

db/drizzle.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useMigrations } from "drizzle-orm/expo-sqlite/migrator";
44

55
import migrations from "./migrations/migrations";
66

7-
const expoDb = openDatabaseSync("database.db");
7+
const expoDb = openDatabaseSync("database.db", { enableChangeListener: true });
88
const db = drizzle(expoDb);
99

1010
export const initialize = (): Promise<ExpoSQLiteDatabase> => {

0 commit comments

Comments
 (0)