Skip to content

Commit 36e43cc

Browse files
authored
chore(clerk-js, shared): Remove SWR from clerk-js and create variants for existing internal hooks (#7270)
1 parent 69833fe commit 36e43cc

File tree

68 files changed

+1873
-519
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1873
-519
lines changed

.changeset/early-clowns-dance.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
---
4+
5+
Removes SWR as direct dependency

.changeset/fuzzy-donuts-jam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/shared': minor
3+
---
4+
5+
Creates compatibility layer for SWR hooks that were previously inside `@clerk/clerk-js`

.github/workflows/ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ jobs:
310310
'vue',
311311
'nuxt',
312312
'react-router',
313-
'machine',
314313
'custom',
315314
]
316315
test-project: ["chrome"]
@@ -321,6 +320,12 @@ jobs:
321320
- test-name: 'billing'
322321
test-project: 'chrome'
323322
clerk-use-rq: 'true'
323+
- test-name: 'machine'
324+
test-project: 'chrome'
325+
clerk-use-rq: 'false'
326+
- test-name: 'machine'
327+
test-project: 'chrome'
328+
clerk-use-rq: 'true'
324329
- test-name: 'nextjs'
325330
test-project: 'chrome'
326331
next-version: '14'

integration/tests/machine-auth/component.test.ts

Lines changed: 372 additions & 0 deletions
Large diffs are not rendered by default.

packages/clerk-js/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@
8282
"dequal": "2.0.3",
8383
"input-otp": "1.4.2",
8484
"qrcode.react": "4.2.0",
85-
"regenerator-runtime": "0.14.1",
86-
"swr": "2.3.4"
85+
"regenerator-runtime": "0.14.1"
8786
},
8887
"devDependencies": {
8988
"@clerk/testing": "workspace:^",

packages/clerk-js/src/ui/components/APIKeys/APIKeys.tsx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { isClerkAPIResponseError } from '@clerk/shared/error';
22
import { __experimental_useAPIKeys as useAPIKeys, useClerk, useOrganization, useUser } from '@clerk/shared/react';
3-
import type { CreateAPIKeyParams } from '@clerk/shared/types';
3+
import type { APIKeyResource } from '@clerk/shared/types';
44
import { lazy, useState } from 'react';
5-
import useSWRMutation from 'swr/mutation';
65

76
import { useProtect } from '@/ui/common';
87
import { useAPIKeysContext, withCoreUserGuard } from '@/ui/contexts';
@@ -69,6 +68,7 @@ export const APIKeysPage = ({ subject, perPage, revokeModalRoot }: APIKeysPagePr
6968
fetchPage,
7069
pageCount,
7170
count: itemCount,
71+
revalidate: invalidateAll,
7272
} = useAPIKeys({
7373
subject,
7474
pageSize: perPage ?? API_KEYS_PAGE_SIZE,
@@ -77,12 +77,11 @@ export const APIKeysPage = ({ subject, perPage, revokeModalRoot }: APIKeysPagePr
7777
enabled: isOrg ? canReadAPIKeys : true,
7878
});
7979

80-
const { invalidateAll } = useAPIKeysPagination({
80+
useAPIKeysPagination({
8181
query,
8282
page,
8383
pageCount,
8484
isFetching,
85-
subject,
8685
fetchPage,
8786
});
8887

@@ -94,11 +93,9 @@ export const APIKeysPage = ({ subject, perPage, revokeModalRoot }: APIKeysPagePr
9493
};
9594
const card = useCardState();
9695
const clerk = useClerk();
97-
const {
98-
data: createdAPIKey,
99-
trigger: createAPIKey,
100-
isMutating,
101-
} = useSWRMutation('api-keys-create', (_key, { arg }: { arg: CreateAPIKeyParams }) => clerk.apiKeys.create(arg));
96+
97+
const [apiKey, setAPIKey] = useState<APIKeyResource | null>(null);
98+
10299
const { t } = useLocalizations();
103100
const [isRevokeModalOpen, setIsRevokeModalOpen] = useState(false);
104101
const [selectedAPIKeyID, setSelectedAPIKeyID] = useState('');
@@ -107,19 +104,23 @@ export const APIKeysPage = ({ subject, perPage, revokeModalRoot }: APIKeysPagePr
107104

108105
const handleCreateAPIKey = async (params: OnCreateParams) => {
109106
try {
110-
await createAPIKey({
107+
card.setLoading();
108+
const apiKey = await clerk.apiKeys.create({
111109
...params,
112110
subject,
113111
});
114112
invalidateAll();
115113
card.setError(undefined);
116114
setIsCopyModalOpen(true);
115+
setAPIKey(apiKey);
117116
} catch (err: any) {
118117
if (isClerkAPIResponseError(err)) {
119118
if (err.status === 409) {
120119
card.setError('API Key name already exists');
121120
}
122121
}
122+
} finally {
123+
card.setIdle();
123124
}
124125
};
125126

@@ -181,10 +182,7 @@ export const APIKeysPage = ({ subject, perPage, revokeModalRoot }: APIKeysPagePr
181182
<Action.Open value='add-api-key'>
182183
<Flex sx={t => ({ paddingTop: t.space.$6, paddingBottom: t.space.$6 })}>
183184
<Action.Card sx={{ width: '100%' }}>
184-
<CreateAPIKeyForm
185-
onCreate={handleCreateAPIKey}
186-
isSubmitting={isMutating}
187-
/>
185+
<CreateAPIKeyForm onCreate={handleCreateAPIKey} />
188186
</Action.Card>
189187
</Flex>
190188
</Action.Open>
@@ -193,8 +191,8 @@ export const APIKeysPage = ({ subject, perPage, revokeModalRoot }: APIKeysPagePr
193191
isOpen={isCopyModalOpen}
194192
onOpen={() => setIsCopyModalOpen(true)}
195193
onClose={() => setIsCopyModalOpen(false)}
196-
apiKeyName={createdAPIKey?.name ?? ''}
197-
apiKeySecret={createdAPIKey?.secret ?? ''}
194+
apiKeyName={apiKey?.name || ''}
195+
apiKeySecret={apiKey?.secret || ''}
198196
modalRoot={revokeModalRoot}
199197
/>
200198
</Action.Root>

packages/clerk-js/src/ui/components/APIKeys/CreateAPIKeyForm.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, { useMemo, useRef, useState } from 'react';
33
import { useAPIKeysContext } from '@/ui/contexts';
44
import { Box, Col, descriptors, FormLabel, localizationKeys, Text, useLocalizations } from '@/ui/customizables';
55
import { useActionContext } from '@/ui/elements/Action/ActionRoot';
6+
import { useCardState } from '@/ui/elements/contexts';
67
import { Form } from '@/ui/elements/Form';
78
import { FormButtons } from '@/ui/elements/FormButtons';
89
import { FormContainer } from '@/ui/elements/FormContainer';
@@ -28,7 +29,6 @@ export type OnCreateParams = {
2829

2930
interface CreateAPIKeyFormProps {
3031
onCreate: (params: OnCreateParams) => void;
31-
isSubmitting: boolean;
3232
}
3333

3434
const EXPIRATION_DURATIONS: Record<Exclude<Expiration, 'never'>, (date: Date) => void> = {
@@ -117,10 +117,11 @@ const ExpirationSelector: React.FC<ExpirationSelectorProps> = ({ selectedExpirat
117117
);
118118
};
119119

120-
export const CreateAPIKeyForm: React.FC<CreateAPIKeyFormProps> = ({ onCreate, isSubmitting }) => {
120+
export const CreateAPIKeyForm: React.FC<CreateAPIKeyFormProps> = ({ onCreate }) => {
121121
const [selectedExpiration, setSelectedExpiration] = useState<ExpirationOption | null>(null);
122122
const { close: closeCardFn } = useActionContext();
123123
const { showDescription = false } = useAPIKeysContext();
124+
const card = useCardState();
124125
const { t } = useLocalizations();
125126

126127
const nameField = useFormControl('name', '', {
@@ -251,7 +252,7 @@ export const CreateAPIKeyForm: React.FC<CreateAPIKeyFormProps> = ({ onCreate, is
251252
submitLabel={localizationKeys('apiKeys.formButtonPrimary__add')}
252253
isDisabled={!canSubmit}
253254
onReset={closeCardFn}
254-
isLoading={isSubmitting}
255+
isLoading={card.isLoading}
255256
elementDescriptor={descriptors.apiKeysCreateFormSubmitButton}
256257
/>
257258
</Form.Root>
Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import { useCallback, useEffect, useRef } from 'react';
2-
import { useSWRConfig } from 'swr';
1+
import { useEffect, useRef } from 'react';
32

43
type UseAPIKeysPaginationParams = {
54
query: string;
65
page: number;
76
pageCount: number;
87
isFetching: boolean;
9-
subject: string;
108
fetchPage: (page: number) => void;
119
};
1210

@@ -16,27 +14,7 @@ type UseAPIKeysPaginationParams = {
1614
* - Adjusts page when current page exceeds available pages (e.g., after deletion)
1715
* - Provides cache invalidation function for mutations
1816
*/
19-
export const useAPIKeysPagination = ({
20-
query,
21-
page,
22-
pageCount,
23-
isFetching,
24-
subject,
25-
fetchPage,
26-
}: UseAPIKeysPaginationParams) => {
27-
const { mutate } = useSWRConfig();
28-
29-
// Invalidate all cache entries for this user or organization
30-
const invalidateAll = useCallback(() => {
31-
void mutate(key => {
32-
if (!key || typeof key !== 'object') {
33-
return false;
34-
}
35-
// Match all apiKeys cache entries for this user or organization, regardless of page, pageSize, or query
36-
return 'type' in key && key.type === 'apiKeys' && 'subject' in key && key.subject === subject;
37-
});
38-
}, [mutate, subject]);
39-
17+
export const useAPIKeysPagination = ({ query, page, pageCount, isFetching, fetchPage }: UseAPIKeysPaginationParams) => {
4018
// Reset to first page when query changes
4119
const previousQueryRef = useRef(query);
4220
useEffect(() => {
@@ -53,8 +31,4 @@ export const useAPIKeysPagination = ({
5331
fetchPage(Math.max(1, pageCount));
5432
}
5533
}, [pageCount, page, isFetching, fetchPage]);
56-
57-
return {
58-
invalidateAll,
59-
};
6034
};

packages/clerk-js/src/ui/components/PaymentAttempts/PaymentAttemptPage.tsx

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { useClerk, useOrganizationContext } from '@clerk/shared/react';
1+
import { __internal_usePaymentAttemptQuery } from '@clerk/shared/react/index';
22
import type { BillingSubscriptionItemResource } from '@clerk/shared/types';
3-
import useSWR from 'swr';
43

54
import { Alert } from '@/ui/elements/Alert';
65
import { Header } from '@/ui/elements/Header';
@@ -31,28 +30,17 @@ export const PaymentAttemptPage = () => {
3130
const subscriberType = useSubscriberTypeContext();
3231
const localizationRoot = useSubscriberTypeLocalizationRoot();
3332
const { t, translateError } = useLocalizations();
34-
const clerk = useClerk();
35-
// Do not use `useOrganization` to avoid triggering the in-app enable organizations prompt in development instance
36-
const organizationCtx = useOrganizationContext();
33+
const requesterType = subscriberType === 'organization' ? 'organization' : 'user';
3734

3835
const {
3936
data: paymentAttempt,
4037
isLoading,
4138
error,
42-
} = useSWR(
43-
params.paymentAttemptId
44-
? {
45-
type: 'payment-attempt',
46-
id: params.paymentAttemptId,
47-
orgId: subscriberType === 'organization' ? organizationCtx?.organization?.id : undefined,
48-
}
49-
: null,
50-
() =>
51-
clerk.billing.getPaymentAttempt({
52-
id: params.paymentAttemptId,
53-
orgId: subscriberType === 'organization' ? organizationCtx?.organization?.id : undefined,
54-
}),
55-
);
39+
} = __internal_usePaymentAttemptQuery({
40+
paymentAttemptId: params.paymentAttemptId,
41+
for: requesterType,
42+
enabled: Boolean(params.paymentAttemptId),
43+
});
5644

5745
const subscriptionItem = paymentAttempt?.subscriptionItem;
5846

packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useClerk } from '@clerk/shared/react';
1+
import { __internal_usePlanDetailsQuery } from '@clerk/shared/react/index';
22
import type {
33
__internal_PlanDetailsProps,
44
BillingPlanResource,
@@ -7,7 +7,6 @@ import type {
77
} from '@clerk/shared/types';
88
import * as React from 'react';
99
import { useMemo, useState } from 'react';
10-
import useSWR from 'swr';
1110

1211
import { Alert } from '@/ui/elements/Alert';
1312
import { Avatar } from '@/ui/elements/Avatar';
@@ -79,24 +78,17 @@ const PlanDetailsInternal = ({
7978
plan: initialPlan,
8079
initialPlanPeriod = 'month',
8180
}: __internal_PlanDetailsProps) => {
82-
const clerk = useClerk();
8381
const [planPeriod, setPlanPeriod] = useState<BillingSubscriptionPlanPeriod>(initialPlanPeriod);
8482

8583
const {
8684
data: plan,
8785
isLoading,
8886
error,
89-
} = useSWR<BillingPlanResource, ClerkAPIResponseError>(
90-
planId || initialPlan ? { type: 'plan', id: planId || initialPlan?.id } : null,
91-
// @ts-expect-error we are handling it above
92-
() => clerk.billing.getPlan({ id: planId || initialPlan?.id }),
93-
{
94-
fallbackData: initialPlan,
95-
revalidateOnFocus: false,
96-
shouldRetryOnError: false,
97-
keepPreviousData: true,
98-
},
99-
);
87+
} = __internal_usePlanDetailsQuery({
88+
planId,
89+
initialPlan,
90+
enabled: Boolean(planId || initialPlan?.id),
91+
});
10092

10193
if (isLoading && !initialPlan) {
10294
return (

0 commit comments

Comments
 (0)