Skip to content

Commit

Permalink
FIX: turns out tanstack docs suck and break your code
Browse files Browse the repository at this point in the history
  • Loading branch information
ejmg committed Jul 12, 2024
1 parent a0e39fa commit 1f38b31
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 119 deletions.
5 changes: 3 additions & 2 deletions src/app/blocks/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { BlocksTable } from "@/components/BlocksTable";
import getBlocks from "@/components/BlocksTable/getBlocks";
import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query";
import { getQueryClient } from "@/lib/utils";
import { HydrationBoundary, dehydrate } from "@tanstack/react-query";

export const dynamic = "force-dynamic";

const Page = () => {
const queryClient = new QueryClient();
const queryClient = getQueryClient();

const defaultQueryOptions = {
pageIndex: 0,
Expand Down
61 changes: 20 additions & 41 deletions src/app/transaction/[hash]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,41 @@
"use client";

// "use client";
import { type FC } from "react";
import { useQuery } from "@tanstack/react-query";
import { TransactionResult } from "@/lib/validators/search";
import axios from "axios";
import Transaction from "@/components/Transaction";
import { HydrationBoundary, dehydrate } from "@tanstack/react-query";
import { Transaction } from "@/components/Transaction";
import getTransaction from "@/components/Transaction/getTransaction";
import { getQueryClient } from "@/lib/utils";

interface PageProps {
params: {
hash: string
}
}

// TODO: this entire page could probably be rendered statically on the server via layout.ts & some minor optimization via tanstack query context.
const Page : FC<PageProps> = ({ params }) => {
const { hash } = params;

const { data: txData , isError } = useQuery({
queryFn: async () => {
console.log(`Fetching: GET /api/transaction?q=${hash}`);
const { data } = await axios.get(`/api/transaction?q=${hash}`);
console.log("Fetched result:", data);
const result = TransactionResult.safeParse(data);
const queryClient = getQueryClient();

if (result.success) {
return result.data;
} else {
throw new Error(result.error.message);
}
},
queryKey: ["txQuery", hash],
retry: false,
const endpoint = "/api/transaction/";
const queryName = "txQuery";
const errorMessage = "Failed to query transaction, please try reloading the page.";

queryClient.prefetchQuery({
queryKey: [queryName, hash],
queryFn: () => getTransaction({ endpoint, hash }),
meta: {
errorMessage: "Failed to find transaction event with provided hash. Please check hash or try a different query.",
errorMessage,
},
});

if (isError) {
return (
<div className="py-5 flex justify-center">
<h1 className="text-4xl font-semibold">No results found.</h1>
</div>
);
}

// TODO: Replace with data table component views once those are fleshed out.
// TODO: add Suspense
return (
<div className="bg-primary rounded-sm shadow-md">
{txData ? (
<div className="flex flex-col items-center gap-5 pt-5">
<h1 className="sm:text-2xl text-lg font-bold">Transaction Event Summary</h1>
<div className="sm:w-11/12 w-full">
<Transaction txData={txData} />
</div>
<div className="bg-primary flex flex-col gap-5 pt-5 items-center ">
<h1 className="font-medium">Transaction Summary</h1>
<HydrationBoundary state={dehydrate(queryClient)}>
<div className="sm:w-11/12 w-full">
<Transaction {...{endpoint, queryName, hash}}/>
</div>
) : <p>No results</p>
}
</HydrationBoundary>
</div>
);
};
Expand Down
69 changes: 2 additions & 67 deletions src/components/Providers/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use client";

import React, { useState } from "react";
import { QueryCache, QueryClient, QueryClientProvider, defaultShouldDehydrateQuery, isServer } from "@tanstack/react-query";
import { QueryClientProvider } from "@tanstack/react-query";
import { createGrpcWebTransport } from "@connectrpc/connect-web";
import { TransportProvider } from "@connectrpc/connect-query";
import { Toaster } from "../ui/toaster";
import { toast } from "../ui/use-toast";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
import { getQueryClient } from "@/lib/utils";

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
Expand All @@ -17,71 +17,6 @@ const penumbraTransport = createGrpcWebTransport({
baseUrl: "https://grpc.testnet.penumbra.zone",
});

let browserQueryClient: QueryClient | undefined = undefined;

const makeQueryClient = () => {
// const { toast } = useToast();
return new QueryClient({
defaultOptions: {
queries: {
// Direct suggestion by tanstack, to prevent over-eager refetching from the client.
staleTime: 60 * 1000,
},
dehydrate: {
// only successful and pending Queries are included per defaults
shouldDehydrateQuery: (query) =>
defaultShouldDehydrateQuery(query) ||
query.state.status === "pending",
},
},
queryCache: new QueryCache({
onError: (error, query) => {
// TODO: Overall model is fine but need to change how meta is used.
// Idea: Add a `errorTitle` field instead that can be used in place of "Error" below. This gives a top level, succinct explanation.
// `description` becomes whatever value we store inside our error value. This is what needs to be refactored to make all queries play nicely.
// This allows each error to clearly signal its nature while also giving detail where appropriate. The issue of that detail is delegated to the useQuery callsite
// and any component/route that throws errors.
// There may be a more elegant way of expressing this but the general typing of onError's `error` and `query` arguments requires some amount of refinement for safety.
// https://tanstack.com/query/latest/docs/react/reference/QueryCache
let errorMessage = "";
const meta = query?.meta ?? undefined ;
if (meta) {
// Precondition for this type cast: meta is of type Record<string, unknown> where any query with a meta object containing the property `errorMessage` has a value of type string.
errorMessage = meta?.errorMessage as string ?? "";
}
if (errorMessage !== "") {
toast({
variant: "destructive",
title: "Error",
description: `${errorMessage}`,
});
} else {
// TODO: Realistically, this will not be a useful error and should be improved further.
toast({
variant: "destructive",
title: "Error",
description: `${error.message}`,
});
}
},
}),
});
};

const getQueryClient = () => {
if (isServer) {
// Server: always make a new query client
return makeQueryClient();
} else {
// Browser: make a new query client if we don't already have one
// This is very important, so we don't re-make a new client if React
// suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
};


const Providers = ({ children } : { children: React.ReactNode }) => {
// NOTE: there is a very explicit warning in the TanStack docs about using useState for handling QueryClient (de)hydration within the provider in the scenario where
Expand Down
14 changes: 14 additions & 0 deletions src/components/Transaction/getTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TransactionData } from "@/lib/validators/search";

export default async function getTransaction({ endpoint, hash } : { endpoint: string, hash: string}) {
console.log(`Fetching: POST ${endpoint}?q=${hash}`);
const res = await fetch(`http://localhost:3000${endpoint}?q=${hash}`, { method: "GET" });
const json = await res.json();
console.log("Fetched Result:", json);
const result = TransactionData.safeParse(json);
if (result.success) {
return result.data;
} else {
throw new Error(result.error.message);
}
}
23 changes: 14 additions & 9 deletions src/components/Transaction/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { type TransactionResultPayload } from "@/lib/validators/search";
"use client";
import { JsonView, allExpanded, defaultStyles } from "react-json-view-lite";
import "react-json-view-lite/dist/index.css";
import Link from "next/link";
import { type FC } from "react";
import { TransactionView } from "../TransactionView";
import { ibcRegistry } from "@/lib/protobuf";
import { useSuspenseQuery } from "@tanstack/react-query";
import getTransaction from "./getTransaction";

interface TransactionProps {
txData: TransactionResultPayload
// txData: TransactionDataPayload
endpoint: string,
queryName: string,
hash: string,
}

const Transaction : FC<TransactionProps> = ({ txData }) => {
const [txResult, penumbraTx] = txData;
// const abciEvents = txResult.events;
export const Transaction : FC<TransactionProps> = ({ endpoint, queryName, hash }) => {
const { data } = useSuspenseQuery({
queryKey: [queryName, hash],
queryFn: () => getTransaction({ endpoint, hash }),
});

const [ txResult, penumbraTx ] = data;
return (
<div>
<div className="flex flex-wrap justify-between sm:p-5 p-2 sm:gap-y-10 gap-y-5 w-full">
Expand All @@ -34,13 +41,11 @@ const Transaction : FC<TransactionProps> = ({ txData }) => {
<div className="flex flex-wrap justify-start w-full">
<p className="sm:w-1/6 sm:shrink-0 w-full sm:text-lg font-semibold">Transaction Data</p>
<pre className="sm:w-5/6 w-full break-all whitespace-pre-wrap text-xs p-1 bg-slate-100">
<JsonView data={penumbraTx.toJson({ typeRegistry: ibcRegistry }) as object} shouldExpandNode={allExpanded} clickToExpandNode={true} style={defaultStyles}/>
<JsonView data={penumbraTx as object} shouldExpandNode={allExpanded} clickToExpandNode={true} style={defaultStyles}/>
</pre>
</div>
<TransactionView tx={penumbraTx}/>
</div>
</div>
);
};

export default Transaction;

0 comments on commit 1f38b31

Please sign in to comment.