Skip to content

Commit

Permalink
Merge pull request #2 from swan-io/query-optimization
Browse files Browse the repository at this point in the history
Experimentation: query optimization
  • Loading branch information
bloodyowl authored Mar 25, 2024
2 parents ee008dc + 8774168 commit fe48fe6
Show file tree
Hide file tree
Showing 15 changed files with 627 additions and 33 deletions.
62 changes: 62 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const path = require("path");

module.exports = {
parser: "@typescript-eslint/parser",
plugins: ["react", "react-hooks"],

extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
],

parserOptions: {
sourceType: "module",
project: path.resolve(__dirname + "/tsconfig.json"),
},

env: {
browser: true,
es2022: true,
},

overrides: [
{
files: ["**/__{mocks,tests}__/**/*.{ts,tsx}"],
rules: {
"no-empty": ["error", { allowEmptyCatch: true }],
},
},
{
files: ["*.d.ts"],
rules: {
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/no-unused-vars": "off",
},
},
{
files: ["clients/**/src/graphql/**/*.{ts,tsx}"],
rules: {
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-explicit-any": "off",
},
},
],

rules: {
"no-implicit-coercion": "error",
"no-param-reassign": "error",
"no-var": "error",
"object-shorthand": "warn",
"prefer-const": "error",

"no-extra-boolean-cast": "off",

"react/jsx-boolean-value": ["error", "always"],

"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
},

ignorePatterns: [".eslintrc.js"],
};
1 change: 1 addition & 0 deletions docs/docs/use-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ sidebar_label: useQuery
- `variables`: your query variables
- `config` (optional)
- `suspense`: use React Suspense (default: `false`)
- `optimize`: adapt query to only require data that's missing from the cache (default: `false`)

### Returns

Expand Down
53 changes: 32 additions & 21 deletions example/components/AccountMembership.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { FragmentOf, readFragment } from "gql.tada";
import { useState } from "react";
import { P, match } from "ts-pattern";
import { graphql } from "../graphql";
import { AccountMembershipDetail } from "./AccountMembershipDetail";

export const accountMembershipFragment = graphql(`
fragment AccountMembership on AccountMembership {
Expand All @@ -12,7 +14,6 @@ export const accountMembershipFragment = graphql(`
}
statusInfo {
__typename
status
... on AccountMembershipBindingUserErrorStatusInfo {
restrictedTo {
firstName
Expand All @@ -36,27 +37,37 @@ type Props = {
export const AccountMembership = ({ data }: Props) => {
const accountMembership = readFragment(accountMembershipFragment, data);

const [isOpen, setIsOpen] = useState(false);

return (
<div className="AccountMembership">
<strong>{accountMembership.id}</strong>:
{match(accountMembership)
.with(
{ user: { firstName: P.string, lastName: P.string } },
({ user: { firstName, lastName } }) => `${firstName} ${lastName}`,
)
.with(
{
statusInfo: {
restrictedTo: { firstName: P.string, lastName: P.string },
},
},
({
statusInfo: {
restrictedTo: { firstName, lastName },
<>
<div className="AccountMembership" onClick={() => setIsOpen(true)}>
<strong>{accountMembership.id}</strong>:
{match(accountMembership)
.with(
{ user: { firstName: P.string, lastName: P.string } },
({ user: { firstName, lastName } }) => `${firstName} ${lastName}`,
)
.with(
{
statusInfo: {
restrictedTo: { firstName: P.string, lastName: P.string },
},
},
}) => `${firstName} ${lastName} (restricted to)`,
)
.otherwise(() => "No user")}
</div>
({
statusInfo: {
restrictedTo: { firstName, lastName },
},
}) => `${firstName} ${lastName} (restricted to)`,
)
.otherwise(() => "No user")}
</div>
{isOpen ? (
<AccountMembershipDetail
accountMembershipId={accountMembership.id}
onPressClose={() => setIsOpen(false)}
/>
) : null}
</>
);
};
70 changes: 70 additions & 0 deletions example/components/AccountMembershipDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { AsyncData, Result } from "@swan-io/boxed";
import { useState } from "react";
import { P, match } from "ts-pattern";
import { ClientError, useQuery } from "../../src";
import { graphql } from "../graphql";
import { UserCard } from "./UserCard";

export const accountMembershipDetailQuery = graphql(`
query AccountMembershipDetail($accountMembershipId: ID!) {
accountMembership(id: $accountMembershipId) {
id
user {
id
firstName
lastName
}
}
}
`);

type Props = {
accountMembershipId: string;
onPressClose: () => void;
};

export const AccountMembershipDetail = ({
accountMembershipId,
onPressClose,
}: Props) => {
const [data] = useQuery(
accountMembershipDetailQuery,
{ accountMembershipId },
{ optimize: true },
);

const [showDetails, setShowDetails] = useState(false);

return match(data)
.with(AsyncData.P.NotAsked, () => "Nothing")
.with(AsyncData.P.Loading, () => "Loading")
.with(AsyncData.P.Done(Result.P.Error(P.select())), (error) => {
ClientError.forEach(error, (error) => console.error(error));
return "Error";
})
.with(
AsyncData.P.Done(Result.P.Ok(P.select())),
({ accountMembership }) => {
if (accountMembership == null) {
return <div>No membership</div>;
}
return (
<dialog open={true}>
<button onClick={onPressClose}>Close</button>
<div>
<strong>Membership: {accountMembership.id}</strong>

{showDetails ? (
<UserCard accountMembershipId={accountMembership.id} />
) : (
<button onClick={() => setShowDetails(true)}>
Show user info
</button>
)}
</div>
</dialog>
);
},
)
.exhaustive();
};
11 changes: 10 additions & 1 deletion example/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ export const App = () => {
"fa3a2485-43bc-461e-b38c-5a9bc3750c7d",
);
const [suspense, setSuspense] = useState(false);
const [optimize, setOptimize] = useState(false);

const [data, { isLoading, reload, refresh }] = useQuery(
transactionListQuery,
{
accountMembershipId,
after,
},
{ suspense },
{ suspense, optimize },
);

const toggleAccountMembership = useCallback(() => {
Expand Down Expand Up @@ -65,6 +66,14 @@ export const App = () => {
/>
Suspense
</label>
<label>
<input
type="checkbox"
checked={optimize}
onChange={() => setOptimize((x) => !x)}
/>
Optimize
</label>
</header>

{match(data)
Expand Down
80 changes: 80 additions & 0 deletions example/components/UserCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { AsyncData, Result } from "@swan-io/boxed";
import { P, match } from "ts-pattern";
import { ClientError, useQuery } from "../../src";
import { graphql } from "../graphql";

export const accountMembershipUserDetailQuery = graphql(`
query AccountMembershipUserDetail($accountMembershipId: ID!) {
accountMembership(id: $accountMembershipId) {
id
user {
id
firstName
lastName
birthDate
mobilePhoneNumber
}
}
}
`);

type Props = {
accountMembershipId: string;
};

const formatter = new Intl.DateTimeFormat();

export const UserCard = ({ accountMembershipId }: Props) => {
const [data] = useQuery(
accountMembershipUserDetailQuery,
{
accountMembershipId,
},
{ optimize: true },
);

return match(data)
.with(AsyncData.P.NotAsked, () => "Nothing")
.with(AsyncData.P.Loading, () => "Loading")
.with(AsyncData.P.Done(Result.P.Error(P.select())), (error) => {
ClientError.forEach(error, (error) => console.error(error));
return "Error";
})
.with(
AsyncData.P.Done(Result.P.Ok(P.select())),
({ accountMembership }) => {
if (accountMembership == null) {
return null;
}
const user = accountMembership.user;
if (user == null) {
return <div>No user</div>;
}
return (
<div className="User">
<ul>
<li>
<strong>ID:</strong> {user.id}
</li>
<li>
<strong>First name:</strong> {user.firstName}
</li>
<li>
<strong>Last name:</strong> {user.lastName}
</li>
{user.birthDate != null ? (
<li>
<strong>Birthdate:</strong>{" "}
{formatter.format(new Date(user.birthDate))}
</li>
) : null}
<li>
<strong>Mobile phone number:</strong> {user.mobilePhoneNumber}
</li>
</ul>
</div>
);
},
)
.exhaustive();
};
2 changes: 2 additions & 0 deletions example/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ export const graphql = initGraphQLTada<{
ID: string;
Currency: string;
AmountValue: string;
Date: string;
PhoneNumber: string;
};
}>();
6 changes: 6 additions & 0 deletions src/cache/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ export class ClientCache {
});
}

getFromCacheWithoutKey(cacheKey: symbol) {
return this.get(cacheKey).flatMap((entry) => {
return Option.Some(entry.value);
});
}

get(cacheKey: symbol): Option<CacheEntry> {
if (this.cache.has(cacheKey)) {
return Option.Some(this.cache.get(cacheKey) as CacheEntry);
Expand Down
Loading

0 comments on commit fe48fe6

Please sign in to comment.