Removes Tanstack Query
This commit is contained in:
127
README.md
127
README.md
@@ -1,4 +1,4 @@
|
||||
Welcome to your new TanStack app!
|
||||
Welcome to your new TanStack app!
|
||||
|
||||
# Getting Started
|
||||
|
||||
@@ -6,7 +6,7 @@ To run this application:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run start
|
||||
npm run start
|
||||
```
|
||||
|
||||
# Building For Production
|
||||
@@ -29,10 +29,8 @@ npm run test
|
||||
|
||||
This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
|
||||
|
||||
|
||||
|
||||
|
||||
## Routing
|
||||
|
||||
This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`.
|
||||
|
||||
### Adding A Route
|
||||
@@ -68,8 +66,8 @@ In the File Based Routing setup the layout is located in `src/routes/__root.tsx`
|
||||
Here is an example layout that includes a header:
|
||||
|
||||
```tsx
|
||||
import { Outlet, createRootRoute } from '@tanstack/react-router'
|
||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
||||
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
@@ -86,127 +84,18 @@ export const Route = createRootRoute({
|
||||
<TanStackRouterDevtools />
|
||||
</>
|
||||
),
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
The `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout.
|
||||
|
||||
More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
|
||||
|
||||
|
||||
## Data Fetching
|
||||
|
||||
There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.
|
||||
### Pocketbase
|
||||
|
||||
For example:
|
||||
|
||||
```tsx
|
||||
const peopleRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "/people",
|
||||
loader: async () => {
|
||||
const response = await fetch("https://swapi.dev/api/people");
|
||||
return response.json() as Promise<{
|
||||
results: {
|
||||
name: string;
|
||||
}[];
|
||||
}>;
|
||||
},
|
||||
component: () => {
|
||||
const data = peopleRoute.useLoaderData();
|
||||
return (
|
||||
<ul>
|
||||
{data.results.map((person) => (
|
||||
<li key={person.name}>{person.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).
|
||||
|
||||
### React-Query
|
||||
|
||||
React-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze.
|
||||
|
||||
First add your dependencies:
|
||||
|
||||
```bash
|
||||
npm install @tanstack/react-query @tanstack/react-query-devtools
|
||||
```
|
||||
|
||||
Next we'll need to create a query client and provider. We recommend putting those in `main.tsx`.
|
||||
|
||||
```tsx
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
// ...
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
// ...
|
||||
|
||||
if (!rootElement.innerHTML) {
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You can also add TanStack Query Devtools to the root route (optional).
|
||||
|
||||
```tsx
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
|
||||
const rootRoute = createRootRoute({
|
||||
component: () => (
|
||||
<>
|
||||
<Outlet />
|
||||
<ReactQueryDevtools buttonPosition="top-right" />
|
||||
<TanStackRouterDevtools />
|
||||
</>
|
||||
),
|
||||
});
|
||||
```
|
||||
|
||||
Now you can use `useQuery` to fetch your data.
|
||||
|
||||
```tsx
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import "./App.css";
|
||||
|
||||
function App() {
|
||||
const { data } = useQuery({
|
||||
queryKey: ["people"],
|
||||
queryFn: () =>
|
||||
fetch("https://swapi.dev/api/people")
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.results as { name: string }[]),
|
||||
initialData: [],
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ul>
|
||||
{data.map((person) => (
|
||||
<li key={person.name}>{person.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
You can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview).
|
||||
TODO
|
||||
|
||||
## State Management
|
||||
|
||||
|
||||
3
package-lock.json
generated
3
package-lock.json
generated
@@ -9,7 +9,6 @@
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.4",
|
||||
"@headlessui/react": "^2.2.4",
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
"@tanstack/react-query": "^5.79.0",
|
||||
"@tanstack/react-query-devtools": "^5.79.0",
|
||||
"@tanstack/react-router": "^1.114.3",
|
||||
"@tanstack/react-router-devtools": "^1.114.3",
|
||||
@@ -1722,6 +1721,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.79.0.tgz",
|
||||
"integrity": "sha512-s+epTqqLM0/TbJzMAK7OEhZIzh63P9sWz5HEFc5XHL4FvKQXQkcjI8F3nee+H/xVVn7mrP610nVXwOytTSYd0w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
@@ -1742,6 +1742,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.79.0.tgz",
|
||||
"integrity": "sha512-DjC4JIYZnYzxaTzbg3osOU63VNLP67dOrWet2cZvXgmgwAXNxfS52AMq86M5++ILuzW+BqTUEVMTjhrZ7/XBuA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.79.0"
|
||||
},
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.4",
|
||||
"@headlessui/react": "^2.2.4",
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
"@tanstack/react-query": "^5.79.0",
|
||||
"@tanstack/react-query-devtools": "^5.79.0",
|
||||
"@tanstack/react-router": "^1.114.3",
|
||||
"@tanstack/react-router-devtools": "^1.114.3",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createContext, useContext, useCallback } from "react";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { createContext, useContext, useCallback, useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import type { AuthRecord } from "pocketbase";
|
||||
@@ -26,91 +25,47 @@ export interface AuthContextValue {
|
||||
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Fetches the currently authenticated user from PocketBase.
|
||||
*/
|
||||
async function fetchUser(): Promise<AuthRecord | null> {
|
||||
if (pb.authStore.isValid) {
|
||||
return pb.authStore.record;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for authentication context, using TanStack Query for state management.
|
||||
* Provider for authentication context.
|
||||
*/
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: user, isLoading } = useQuery({
|
||||
queryKey: ["auth", "user"],
|
||||
queryFn: fetchUser,
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [user, setUser] = useState<AuthRecord | null>(pb.authStore.record);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const loginMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
password,
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
}) => {
|
||||
await pb.collection("users").authWithPassword(email, password);
|
||||
return fetchUser();
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["auth", "user"] });
|
||||
},
|
||||
});
|
||||
function updateUser() {
|
||||
if (pb.authStore.isValid) {
|
||||
setUser(pb.authStore.record);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
const signupMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
password,
|
||||
passwordConfirm,
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
passwordConfirm: string;
|
||||
}) => {
|
||||
await pb.collection("users").create({ email, password, passwordConfirm });
|
||||
await pb.collection("users").authWithPassword(email, password);
|
||||
return fetchUser();
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["auth", "user"] });
|
||||
},
|
||||
});
|
||||
|
||||
const logoutMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
pb.authStore.clear();
|
||||
return null;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["auth", "user"] });
|
||||
},
|
||||
});
|
||||
|
||||
const login = useCallback(
|
||||
async (email: string, password: string) => {
|
||||
await loginMutation.mutateAsync({ email, password });
|
||||
navigate({ to: "/campaigns" });
|
||||
},
|
||||
[loginMutation],
|
||||
);
|
||||
const login = useCallback(async (email: string, password: string) => {
|
||||
console.log("login");
|
||||
setIsLoading(true);
|
||||
await pb.collection("users").authWithPassword(email, password);
|
||||
updateUser();
|
||||
navigate({ to: "/campaigns" });
|
||||
}, []);
|
||||
|
||||
const signup = useCallback(
|
||||
async (email: string, password: string, passwordConfirm: string) => {
|
||||
await signupMutation.mutateAsync({ email, password, passwordConfirm });
|
||||
console.log("signup");
|
||||
setIsLoading(true);
|
||||
await pb.collection("users").create({ email, password, passwordConfirm });
|
||||
await pb.collection("users").authWithPassword(email, password);
|
||||
updateUser();
|
||||
navigate({ to: "/campaigns" });
|
||||
},
|
||||
[signupMutation],
|
||||
[],
|
||||
);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
await logoutMutation.mutateAsync();
|
||||
console.log("logout");
|
||||
pb.authStore.clear();
|
||||
setUser(null);
|
||||
navigate({ to: "/" });
|
||||
}, [logoutMutation]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
|
||||
10
src/main.tsx
10
src/main.tsx
@@ -1,7 +1,6 @@
|
||||
import { StrictMode } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
// Import the generated route tree
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
@@ -9,16 +8,13 @@ import { routeTree } from "./routeTree.gen";
|
||||
import "./styles.css";
|
||||
import reportWebVitals from "./reportWebVitals.ts";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
// Create a new router instance
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
context: { queryClient },
|
||||
defaultPreload: "intent",
|
||||
scrollRestoration: true,
|
||||
defaultStructuralSharing: true,
|
||||
defaultPreloadStaleTime: 0,
|
||||
defaultPendingMinMs: 0,
|
||||
});
|
||||
|
||||
// Register the router instance for type safety
|
||||
@@ -34,9 +30,7 @@ if (rootElement && !rootElement.innerHTML) {
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
</QueryClientProvider>
|
||||
<RouterProvider router={router} />
|
||||
</StrictMode>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AuthProvider } from "@/context/auth/AuthContext";
|
||||
import { DocumentProvider } from "@/context/document/DocumentContext";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||
|
||||
@@ -13,7 +12,6 @@ export const Route = createRootRoute({
|
||||
</DocumentProvider>
|
||||
</AuthProvider>
|
||||
<TanStackRouterDevtools />
|
||||
<ReactQueryDevtools buttonPosition="bottom-right" />
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useCallback } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { SessionRow } from "@/components/documents/session/SessionRow";
|
||||
import { Button } from "@headlessui/react";
|
||||
import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { Loader } from "@/components/Loader";
|
||||
import type { Relationship } from "@/lib/types";
|
||||
import type { Campaign, Relationship, Session } from "@/lib/types";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_app/_authenticated/campaigns/$campaignId",
|
||||
@@ -15,14 +14,15 @@ export const Route = createFileRoute(
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const queryClient = useQueryClient();
|
||||
const params = Route.useParams();
|
||||
|
||||
const {
|
||||
data: { campaign, sessions },
|
||||
} = useSuspenseQuery({
|
||||
queryKey: ["campaign"],
|
||||
queryFn: async () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [campaign, setCampaign] = useState<Campaign | null>(null);
|
||||
const [sessions, setSessions] = useState<Session[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
setLoading(true);
|
||||
const campaign = await pb
|
||||
.collection("campaigns")
|
||||
.getOne(params.campaignId);
|
||||
@@ -31,14 +31,17 @@ function RouteComponent() {
|
||||
filter: `campaign = "${params.campaignId}" && type = 'session'`,
|
||||
sort: "-created",
|
||||
});
|
||||
return {
|
||||
campaign,
|
||||
sessions,
|
||||
};
|
||||
},
|
||||
});
|
||||
setSessions(sessions as Session[]);
|
||||
setCampaign(campaign as Campaign);
|
||||
setLoading(false);
|
||||
}
|
||||
fetchData();
|
||||
}, [setCampaign, setSessions, setLoading]);
|
||||
|
||||
const createNewSession = useCallback(async () => {
|
||||
if (campaign === null) {
|
||||
return;
|
||||
}
|
||||
// Check for a previous session
|
||||
const prevSession = await pb
|
||||
.collection("documents")
|
||||
@@ -70,10 +73,12 @@ function RouteComponent() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ["campaign"] });
|
||||
}, [campaign]);
|
||||
|
||||
if (loading || campaign === null) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-xl mx-auto py-8">
|
||||
<div className="mb-2">
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { DocumentPrintRow } from "@/components/documents/DocumentPrintRow";
|
||||
import { SessionPrintRow } from "@/components/documents/session/SessionPrintRow";
|
||||
import { Loader } from "@/components/Loader";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { RelationshipType, type Relationship, type Session } from "@/lib/types";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useDocument, useDocumentCache } from "@/context/document/hooks";
|
||||
import { RelationshipType, type DocumentId, type Session } from "@/lib/types";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import _ from "lodash";
|
||||
|
||||
@@ -16,32 +15,28 @@ export const Route = createFileRoute(
|
||||
|
||||
function RouteComponent() {
|
||||
const params = Route.useParams();
|
||||
const {
|
||||
data: { session, relationships },
|
||||
} = useSuspenseQuery({
|
||||
queryKey: ["session", "relationships"],
|
||||
queryFn: async () => {
|
||||
const session = await pb
|
||||
.collection("documents")
|
||||
.getOne(params.documentId);
|
||||
const relationships: Relationship[] = await pb
|
||||
.collection("relationships")
|
||||
.getFullList({
|
||||
filter: `primary = "${params.documentId}"`,
|
||||
expand: "secondary",
|
||||
});
|
||||
console.log("Fetched data: ", relationships);
|
||||
return {
|
||||
session: session as Session,
|
||||
relationships: _.mapValues(
|
||||
_.groupBy(relationships, (r) => r.type),
|
||||
(rs: Relationship[]) => rs.flatMap((r) => r.expand?.secondary),
|
||||
),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
console.log("Parsed data: ", relationships);
|
||||
const { cache } = useDocumentCache();
|
||||
const { docResult } = useDocument(params.documentId as DocumentId);
|
||||
|
||||
if (docResult.type !== "ready") {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
const session = docResult.value.doc as Session;
|
||||
const relationships = _.mapValues(
|
||||
docResult.value.relationships,
|
||||
(relResult) => {
|
||||
if (relResult.type != "ready") {
|
||||
return [];
|
||||
}
|
||||
return relResult.value.secondary
|
||||
.map((id) => cache.documents[id])
|
||||
.flatMap((docResult) =>
|
||||
docResult.type === "ready" ? [docResult.value.doc] : [],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="fill-w py-8 columns-2 gap-8 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user