feat(guides): integrate GuideContext and persist new guides to IndexedDB

- Wrap application root with IDBProvider and GuideProvider
    - Replace mock data in App component with real guides from useGuides hook
    - Update useNewGuide hook to save compiled guides to IndexedDB and update global state
This commit is contained in:
johannes-hernehult 2026-06-17 11:12:25 +02:00
parent f44b5a9133
commit 7cdeb98161
6 changed files with 150 additions and 9 deletions

View file

@ -2,13 +2,10 @@ import "./styles/reset.css";
import "./styles/App.css";
import { Outlet } from "react-router";
import { Link } from "react-router";
import { useGuides } from "./lib/contexts/guide.context";
function App() {
const guides = [
{ id: 1, title: "Guide 1" },
{ id: 2, title: "Guide 2" },
{ id: 3, title: "Guide 3" },
];
const { guides } = useGuides();
return (
<>
@ -18,7 +15,6 @@ function App() {
</h1>
<nav>
<Link to="/new-guide">Create New Guide</Link>
<ul>
{guides.map((guide) => (
<li key={guide.id}>

View file

@ -4,8 +4,13 @@ import {
type FrameType,
type GuideType,
} from "../../../lib/schemas";
import { useIDB } from "../../../lib/contexts/dbinit.context";
import { useGuides } from "../../../lib/contexts/guide.context";
export default function useNewGuide() {
const { db } = useIDB();
const { setGuides } = useGuides();
const [guide, setGuide] = useState<GuideType>({
id: crypto.randomUUID(),
title: "",
@ -107,7 +112,7 @@ export default function useNewGuide() {
);
};
const handleSaveGuide = (e: React.SubmitEvent<HTMLFormElement>) => {
const handleSaveGuide = async (e: React.SubmitEvent<HTMLFormElement>) => {
e.preventDefault();
const compiledGuide: CompiledGuideType = {
@ -120,7 +125,19 @@ export default function useNewGuide() {
frames,
};
console.log(compiledGuide);
if (!db) return;
await db.put("guides", compiledGuide);
setGuide({
id: crypto.randomUUID(),
title: "",
description: "",
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: undefined,
});
setFrames([]);
setGuides((prev) => [compiledGuide, ...prev]);
};
return {

View file

@ -0,0 +1,55 @@
// IDBContext.tsx
import {
createContext,
useContext,
useEffect,
useState,
type ReactNode,
} from "react";
import { initDB, type AppDB } from "../db";
interface IDBContextValue {
db: AppDB | null;
loading: boolean;
error: Error | null;
}
const IDBContext = createContext<IDBContextValue | undefined>(undefined);
export function IDBProvider({ children }: { children: ReactNode }) {
const [db, setDb] = useState<AppDB | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
initDB()
.then((instance) => {
if (!cancelled) setDb(instance);
})
.catch((err) => {
if (!cancelled)
setError(err instanceof Error ? err : new Error(String(err)));
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, []);
return (
<IDBContext.Provider value={{ db, loading, error }}>
{children}
</IDBContext.Provider>
);
}
/* eslint-disable-next-line react-refresh/only-export-components */
export function useIDB() {
const ctx = useContext(IDBContext);
if (!ctx) throw new Error("useIDB must be used within IDBProvider");
return ctx;
}

View file

@ -0,0 +1,48 @@
// GuideContext.tsx
import {
createContext,
useContext,
useEffect,
useState,
type ReactNode,
} from "react";
import type { CompiledGuideType } from "../schemas";
import { useIDB } from "./dbinit.context";
interface GuideContextValue {
guides: CompiledGuideType[];
setGuides: React.Dispatch<React.SetStateAction<CompiledGuideType[]>>;
}
const GuideContext = createContext<GuideContextValue | undefined>(undefined);
export function GuideProvider({ children }: { children: ReactNode }) {
const [guides, setGuides] = useState<CompiledGuideType[]>([]);
const { db } = useIDB();
useEffect(() => {
if (!db) return;
db.getAll("guides").then((fetchedGuides) => {
const sortedGuides = [...fetchedGuides].sort((a, b) => {
return (
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
});
setGuides(sortedGuides);
});
}, [db]);
return (
<GuideContext.Provider value={{ guides, setGuides }}>
{children}
</GuideContext.Provider>
);
}
/* eslint-disable-next-line react-refresh/only-export-components */
export function useGuides() {
const ctx = useContext(GuideContext);
if (!ctx) {
throw new Error("useGuides must be used within a GuideProvider");
}
return ctx;
}

19
src/lib/db.ts Normal file
View file

@ -0,0 +1,19 @@
import { openDB, type DBSchema, type IDBPDatabase } from "idb";
import type { CompiledGuideType } from "./schemas";
export interface MyDB extends DBSchema {
guides: {
key: string;
value: CompiledGuideType;
};
}
export type AppDB = IDBPDatabase<MyDB>;
export function initDB(): Promise<AppDB> {
return openDB<MyDB>("my-guides", 1, {
upgrade(db) {
db.createObjectStore("guides", { keyPath: "id" });
},
});
}

View file

@ -3,9 +3,15 @@ import { createRoot } from "react-dom/client";
import { RouterProvider } from "react-router";
import { router } from "./router.tsx";
import "./styles/index.css";
import { IDBProvider } from "./lib/contexts/dbinit.context.tsx";
import { GuideProvider } from "./lib/contexts/guide.context.tsx"; // Fix import name
createRoot(document.getElementById("root")!).render(
<StrictMode>
<RouterProvider router={router} />
<IDBProvider>
<GuideProvider>
<RouterProvider router={router} />
</GuideProvider>
</IDBProvider>
</StrictMode>,
);