Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose querykey getter and fix some querykey stuff #3302

Merged
merged 51 commits into from Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5392463
1
juliusmarminge Nov 27, 2022
8638767
Merge branch 'main' into julius/querykeys
juliusmarminge Nov 27, 2022
bc702cc
2
juliusmarminge Nov 27, 2022
5b55386
add some tests
juliusmarminge Nov 27, 2022
2711978
failing test
juliusmarminge Nov 27, 2022
30dcc40
simplify
juliusmarminge Nov 27, 2022
d77f3c8
going places
juliusmarminge Nov 27, 2022
36ffd96
docs
juliusmarminge Nov 27, 2022
0a97a6b
more docs
juliusmarminge Nov 27, 2022
217db37
revert example
juliusmarminge Nov 27, 2022
964da9c
docs 3
juliusmarminge Nov 27, 2022
90a9173
add test
juliusmarminge Nov 27, 2022
dc92dcf
fix test
juliusmarminge Nov 27, 2022
5813e19
restructure
juliusmarminge Nov 27, 2022
366107d
revert lock
juliusmarminge Nov 27, 2022
c1d4497
better brackets
juliusmarminge Nov 27, 2022
c909673
better docs
juliusmarminge Nov 27, 2022
4efa3a8
fixy
juliusmarminge Nov 27, 2022
778859d
please not now docu
juliusmarminge Nov 27, 2022
7770be8
imports
juliusmarminge Nov 27, 2022
e9f03e1
Merge branch 'main' into julius/querykeys
juliusmarminge Dec 4, 2022
cca2d30
move it to trpc object instead
juliusmarminge Dec 5, 2022
8b937df
docs
juliusmarminge Dec 5, 2022
c9b59bd
simplify test setup
juliusmarminge Dec 5, 2022
44179fd
make optional
juliusmarminge Dec 5, 2022
2d25187
add test outside react
juliusmarminge Dec 5, 2022
d06701a
add example
juliusmarminge Dec 5, 2022
4b4e12f
fix
juliusmarminge Dec 5, 2022
377e271
fix2
juliusmarminge Dec 5, 2022
c8b8ee8
docs and remove example
juliusmarminge Dec 16, 2022
cdc5808
Merge branch 'main' into julius/querykeys
juliusmarminge Dec 16, 2022
dd8062e
revert downgrade of docusaurus
juliusmarminge Dec 16, 2022
04d5333
Merge branch 'main' into julius/querykeys
juliusmarminge Dec 16, 2022
c92eb39
Merge branch 'main' into julius/querykeys
juliusmarminge Dec 16, 2022
c1b162e
qc -> queryClient
juliusmarminge Dec 16, 2022
c8e3ed7
mby???
juliusmarminge Dec 16, 2022
0e519e2
patch querykey
juliusmarminge Dec 22, 2022
60afce2
fix invalidate
juliusmarminge Dec 22, 2022
72b5ab6
fix old
juliusmarminge Dec 22, 2022
6f195da
regression tests
juliusmarminge Dec 22, 2022
d60dea3
testid
juliusmarminge Dec 22, 2022
f566339
Merge remote-tracking branch 'origin/main' into julius/querykeys
juliusmarminge Dec 22, 2022
5adc7ec
fix
juliusmarminge Dec 22, 2022
957a78c
interop
juliusmarminge Dec 22, 2022
f025343
Merge branch 'main' into julius/querykeys
juliusmarminge Dec 22, 2022
3ceba17
disable interop
juliusmarminge Dec 22, 2022
05f5c8d
comment
juliusmarminge Dec 22, 2022
88b8052
1 more test
juliusmarminge Dec 22, 2022
943fdeb
fix the interop stuff
juliusmarminge Dec 22, 2022
bb26bc1
eeh
juliusmarminge Dec 22, 2022
1396478
Merge branch 'main' into julius/querykeys
kodiakhq[bot] Dec 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions packages/react-query/src/internals/getArrayQueryKey.ts
@@ -1,16 +1,21 @@
export type QueryType = 'query' | 'infinite' | 'any';

export type QueryKey = [
string[],
{ input?: unknown; type?: Exclude<QueryType, 'any'> },
];

/**
* To allow easy interactions with groups of related queries, such as
* invalidating all queries of a router, we use an array as the path when
* storing in tanstack query. This function converts from the `.` separated
* path passed around internally by both the legacy and proxy implementation.
* https://github.com/trpc/trpc/issues/2611
*/
**/
export function getArrayQueryKey(
queryKey: string | [string] | [string, ...unknown[]] | unknown[],
type: QueryType,
): [string[], { input?: unknown; type?: Exclude<QueryType, 'any'> }] {
): QueryKey {
const queryKeyArrayed = Array.isArray(queryKey) ? queryKey : [queryKey];
const [path, input] = queryKeyArrayed;

Expand Down
21 changes: 21 additions & 0 deletions packages/react-query/src/shared/proxy/utilsProxy.ts
Expand Up @@ -29,6 +29,11 @@ import {
TRPCFetchQueryOptions,
contextProps,
} from '../../internals/context';
import {
QueryKey,
QueryType,
getArrayQueryKey,
} from '../../internals/getArrayQueryKey';
import { getQueryKey } from '../../internals/getQueryKey';

type DecorateProcedure<
Expand Down Expand Up @@ -158,6 +163,15 @@ type DecorateProcedure<
getInfiniteData(
input?: inferProcedureInput<TProcedure>,
): InfiniteData<inferTransformedProcedureOutput<TProcedure>> | undefined;

/**
* Method to extract the query key for a procedure
* @link https://trpc.io/docs/useContext#-the-function-i-want-isnt-here
*/
getQueryKey(
input: inferProcedureInput<TProcedure>,
type: QueryType,
): QueryKey;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my phone so I might be missing something, but wouldn't it make sense to have both arguments optional?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cc @TkDodo wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we default to? "query"?

The input would be optional if the procedure's input is optional, but we need the type (I wrote a bit about this up top or in discord with @sachinraja)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's getting the key for sale of invalidation or resetting, any is a good default

If its for getting or changing the cache any might not be the best

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is "any" going to return? does it match both query and infinite ? If so I think it would be a good default for getQueryKey

Copy link
Contributor

@JonParton JonParton Nov 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any returns the query key without the type object in the key, so as long as fuzzy matching is enabled for the methods then it will match both!

Do all methods match fuzzy? Or do the "query" versions only match exact? This may need some documentation on our side!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do all methods match fuzzy?

all methods can, but it's not the default everywhere. Generally speaking:

  • everything that has a plural in its name is fuzzy, like getQueriesData, invalidateQueries or setQueriesDefault
  • everything that is singular defaults to exact, like getQueryData

Copy link
Contributor

@JonParton JonParton Nov 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fuzzy matching behaviour queried above for xxxxxQuery type methods also effects if the input param should be optional!... Although I think it should be as I know some deffinitely fuzzy match!

(P. S. My phone hadn't updated before TkDodo replied above!)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmn this may mean making things optional / default to a query key of any for the main path I expect this being used for (we provide many of the tanstack methods for individual queries already so this is likely to be used in the fuzzy matching ones!) but then put some good documentation on this noting the gotcha between needing exact keys for methods that are named xxxxxQuery

};

/**
Expand All @@ -174,6 +188,12 @@ type DecorateRouter = {
filters?: InvalidateQueryFilters,
options?: InvalidateOptions,
): Promise<void>;

/**
* Method to extract the query key for a router
* @link https://trpc.io/docs/useContext#-the-function-i-want-isnt-here
*/
getQueryKey(input: undefined, type: 'any'): QueryKey;
};

/**
Expand Down Expand Up @@ -265,6 +285,7 @@ export function createReactQueryUtilsProxy<
context.setInfiniteQueryData(queryKey, updater, ...rest),
getData: () => context.getQueryData(queryKey),
getInfiniteData: () => context.getInfiniteQueryData(queryKey),
getQueryKey: () => getArrayQueryKey(queryKey, rest[0]),
};

return contextMap[utilName]();
Expand Down
118 changes: 118 additions & 0 deletions packages/tests/server/react/useContext.test.tsx
@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { getServerAndReactClient } from './__reactHelpers';
import { useIsFetching } from '@tanstack/react-query';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { initTRPC } from '@trpc/server/src/core';
Expand Down Expand Up @@ -798,4 +799,121 @@ describe('query keys are stored separtely', () => {
`);
expect(data.infinite).toBeUndefined();
});

describe('getQueryKeys', () => {
test('no input', async () => {
const { proxy, App } = ctx;

function MyComponent() {
const utils = proxy.useContext();
const happy = utils.post.all.getQueryKey(undefined, 'query');

// @ts-expect-error - post.all has no input
const sad1 = utils.post.all.getQueryKey('foo');
// @ts-expect-error - need to specify type
const sad2 = utils.post.all.getQueryKey(undefined);

return <pre data-testid="qKey">{JSON.stringify(happy)}</pre>;
}

const utils = render(
<App>
<MyComponent />
</App>,
);

await waitFor(() => {
expect(utils.getByTestId('qKey')).toHaveTextContent(
JSON.stringify([['post', 'all'], { type: 'query' }]),
);
});
});

test('with input', async () => {
const { proxy, App } = ctx;

function MyComponent() {
const utils = proxy.useContext();
const happy = utils.post.byId.getQueryKey({ id: 1 }, 'query');

// @ts-expect-error - post.byId has required input
const sad1 = utils.post.byId.getQueryKey(undefined, 'query');
// @ts-expect-error - need to specify type
const sad2 = utils.post.byId.getQueryKey({ id: 1 });

return <pre data-testid="qKey">{JSON.stringify(happy)}</pre>;
}

const utils = render(
<App>
<MyComponent />
</App>,
);

await waitFor(() => {
expect(utils.getByTestId('qKey')).toHaveTextContent(
JSON.stringify([
['post', 'byId'],
{ input: { id: 1 }, type: 'query' },
]),
);
});
});

test('on router', async () => {
const { proxy, App } = ctx;

function MyComponent() {
const utils = proxy.useContext();
const happy = utils.post.getQueryKey(undefined, 'any');

// @ts-expect-error - router has no input
const sad = utils.post.getQueryKey('foo', 'any');

return (
<div>
<pre data-testid="qKey">{JSON.stringify(happy)}</pre>
</div>
);
}

const utils = render(
<App>
<MyComponent />
</App>,
);

await waitFor(() => {
expect(utils.getByTestId('qKey')).toHaveTextContent(
JSON.stringify([['post'], {}]),
);
});
});

test('forwarded to a real method', async () => {
const { proxy, App } = ctx;

function MyComponent() {
proxy.post.all.useQuery();

const utils = proxy.useContext();
const qKey = utils.post.all.getQueryKey(undefined, 'query');
const isFetching = useIsFetching(qKey);

return <div>{isFetching}</div>;
}

const utils = render(
<App>
<MyComponent />
</App>,
);

// should be fetching initially, and then not
expect(utils.container).toHaveTextContent('1');
await waitFor(() => {
expect(utils.container).toHaveTextContent('0');
});
juliusmarminge marked this conversation as resolved.
Show resolved Hide resolved
});
});
});