feat(shared): Organization hooks with setData and revalidate#1745
Conversation
🦋 Changeset detectedLatest commit: d760fbc The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
f6a0374 to
3ef2f60
Compare
|
!snapshot |
|
Hey @panteliselef - the snapshot version command generated the following package versions:
Tip: use the snippet copy button below to quickly install the required packages. # @clerk/backend
npm i @clerk/backend@0.29.1-snapshot.3ef2f60# @clerk/chrome-extension
npm i @clerk/chrome-extension@0.4.0-snapshot.3ef2f60# @clerk/clerk-js
npm i @clerk/clerk-js@4.58.0-snapshot.3ef2f60# @clerk/clerk-expo
npm i @clerk/clerk-expo@0.19.2-snapshot.3ef2f60# @clerk/fastify
npm i @clerk/fastify@0.6.7-snapshot.3ef2f60# gatsby-plugin-clerk
npm i gatsby-plugin-clerk@4.4.8-snapshot.3ef2f60# @clerk/localizations
npm i @clerk/localizations@1.26.0-snapshot.3ef2f60# @clerk/nextjs
npm i @clerk/nextjs@4.24.0-snapshot.3ef2f60# @clerk/clerk-react
npm i @clerk/clerk-react@4.25.0-snapshot.3ef2f60# @clerk/remix
npm i @clerk/remix@2.10.3-snapshot.3ef2f60# @clerk/clerk-sdk-node
npm i @clerk/clerk-sdk-node@4.12.6-snapshot.3ef2f60# @clerk/shared
npm i @clerk/shared@0.22.1-snapshot.3ef2f60# @clerk/types
npm i @clerk/types@3.52.0-snapshot.3ef2f60 |
3ef2f60 to
04ce85c
Compare
|
@panteliselef can you please elaborate on this? So far we have been using SWR internally for our own convenience. Did customers ask for this? In Clerk the preferred way to do mutations is via the Clerk models that hold all the mutating methods. |
Yes, there were customer requests, I'll share more internally. A developer lists the suggestions of a user and has an accept button for each of those. I'd image the onClick function would contain something like this. const updatedSuggestion = await suggestion.accept()
userSuggestions.mutate.(oldCache => replaceUpdatedItem(updatedSuggestion, oldCache), {
// Since `accept` gives back the updated information,
// we don't need to revalidate here.
revalidate: false,
}) |
|
I see. Let's take it offline. We should be able to support this use case using the Clerk hooks API surface. |
e12b2d1 to
647a5ea
Compare
setCache and revalidate
a2fdb57 to
7a3edc9
Compare
setCache and revalidatesetData and revalidate
| '@clerk/shared': patch | ||
| --- | ||
|
|
||
| Expose mutate for paginated lists of data in organization hooks. |
There was a problem hiding this comment.
❓ Is this description up to date?
There was a problem hiding this comment.
I just updated this one
| } | ||
|
|
||
| return itemsInfinitePages.map(item => { | ||
| if (typeof item === 'undefined') { |
There was a problem hiding this comment.
| if (typeof item === 'undefined') { | |
| if (!item) { return } |
| revalidate: false, | ||
| }); | ||
|
|
||
| const revalidate = triggerInfinite ? () => swrInfiniteMutate() : () => swrMutate(); |
There was a problem hiding this comment.
As described in the PR, the revalidate will refetch all loaded pages when triggerInfinite is true. This behavior can be disabled.
What's the use case where we need to reload all pages?
There was a problem hiding this comment.
regarding refetchAll, this only works when swr auto revalidates, unfortunately when mutate is called explicitly then all pages will be revalidated sequentially
There was a problem hiding this comment.
@SokratisVidros As promised I checked also the implementation of react-query. Seems like it is also following the same pattern, more about it here.
There was a problem hiding this comment.
Not a fan at all. Do we have a use case for this already are can we defer it?
| data?: CData | ((currentData?: CData) => Promise<undefined | CData> | undefined | CData), | ||
| ) => Promise<CData | undefined>; | ||
|
|
||
| export type PaginatedResources<T = unknown, Infinite = false, ArrayOrPaginated = unknown> = { |
There was a problem hiding this comment.
❓ Regardless of infinite pagination or not, the resource data should be stored in a single format in the JS struct. They should always be stored in a paginated struct. Is there a reason we need to support plain arrays? If not, this decision will drastically simplify the following hook declarations.
There was a problem hiding this comment.
I think this is a great catch. Probably I will ditching ArrayOrPaginated completely. Trying to remember why did what i did, but it's been a while so my only theory is that i got destructed by the types return from the resource methods like organization?.getMemberships which can return both an array or the Paginated Object.
Our hooks should only consume those methods and their resolved values to always be Paginated Objects so i will give this another look and probably drop this.
f98bc23 to
b2c6c49
Compare
| } | ||
|
|
||
| return itemsInfinitePages.map(item => { | ||
| if (typeof item === 'undefined') { |
|
@panteliselef We have changed the process of the v4 hotfixing. Please rebase this PR with |
| revalidate: () => Promise<void>; | ||
| setData: Infinite extends true | ||
| ? // Array of pages of data | ||
| CacheSetter<(ClerkPaginatedResponse<T> | undefined)[]> |
There was a problem hiding this comment.
❓ @panteliselef shouldn't this be CacheSetter<(ClerkPaginatedResponse<T>[] | undefined)> ?
There was a problem hiding this comment.
@dimkl a "page" in swr can be either undefined or has a value of type ClerkPaginatedResponse
* feat(shared): Expose stable mutate * chore(clerk-js): OrganizationList and switcher with stable mutate * chore(clerk-js): OrganizationProfile with stable mutate * fix(shared): Replace mutate with setCache and revalidate * fix(clerk-js): Replace mutate with setCache and revalidate * chore(clerk-js,shared): Replace setCache with setData * fix(shared): Remove unnecessary complexity in types * chore(clerk-js): Update after rebase * chore(shared): Add comments with context * chore(shared): Update changeset (cherry picked from commit 743c4d2)
…) (#2013) * feat(shared): Expose stable mutate * chore(clerk-js): OrganizationList and switcher with stable mutate * chore(clerk-js): OrganizationProfile with stable mutate * fix(shared): Replace mutate with setCache and revalidate * fix(clerk-js): Replace mutate with setCache and revalidate * chore(clerk-js,shared): Replace setCache with setData * fix(shared): Remove unnecessary complexity in types * chore(clerk-js): Update after rebase * chore(shared): Add comments with context * chore(shared): Update changeset (cherry picked from commit 743c4d2) Co-authored-by: panteliselef <panteliselef@outlook.com>
Description
Issue:
So far our organization hooks did not officially support mutations. Developers did not have a way to alternate the cache or simply revalidate on demand.
Background:
For
invitationList(from useOrganization) we didn't encounter such issues, because we are forcing an update under the hood whenlastOrganizationMemberchanges. In the case of deleted resource, the hook would refetch and the deleted items would disappear from the cache. In the new flows we introduce with we need to update the cache in place and avoid refetching, in order to avoid layout shifts.We want developers to explicitly call either
revalidateorsetDataafter a resource is updated and let them decide which method best suits their use-case.This PR exposes the mutate function for both useSWR and useSWR infinite based on the parameters a developer provided.
setDatarevalidate. (for example after creating an organization or accepting an invite)mutateis what swr describes as Bound Mutate and does not use useSWRMutation under the hood.As part of the PR, we replaced all usages of unstable__mutate from our update useOrganization and useOrganizationList hooks within ClerkJS Components
setDatawill not trigger a revalidation, it will simply replace the passed data with data already in cacherevalidatedoes not accept any parameter and will perform a revalidation causing a request to fire.Note: I attempted to optimize the revalidation of all pages with infinite by traversing the cache that is only affected by the updated resource.
There is also the case where this approach would result in duplicate keys when rendering lists.
This seems to be quite complicated as swr keeps n+1 records for infinite. That means the if 2 pages are fetched, 3 cache are created. 2 keys directly match the pages data and the 3rd is a "parent" record with its own key and it contains copies of the data of the 2 pages. Manually updating any of them, means we need to update the other as well.
Checklist
npm testruns as expected.npm run buildruns as expected.This will be merged after the PR goes to production [WIP] update: Mention
mutatein useOrganizationList clerk-docs#310Type of change
Packages affected
@clerk/clerk-js@clerk/clerk-react@clerk/nextjs@clerk/remix@clerk/types@clerk/themes@clerk/localizations@clerk/clerk-expo@clerk/backend@clerk/clerk-sdk-node@clerk/shared@clerk/fastify@clerk/chrome-extensiongatsby-plugin-clerkbuild/tooling/chore