Skip to content

ORG-83 userInvitations in useOrganizationList#1520

Merged
panteliselef merged 10 commits into
mainfrom
ORG-59
Aug 9, 2023
Merged

ORG-83 userInvitations in useOrganizationList#1520
panteliselef merged 10 commits into
mainfrom
ORG-59

Conversation

@panteliselef

@panteliselef panteliselef commented Jul 25, 2023

Copy link
Copy Markdown
Contributor

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

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-extension
  • gatsby-plugin-clerk
  • build/tooling/chore

Description

  • npm test runs as expected.
  • npm run build runs as expected.

This PR

  • Creates UserOrganizationInvitation and UserOrganizationInvitationResource
  • Updates useOrganization to return userInvitations
  • Adds the ability to aggregate the userInvitations when fetched from useOrganization (Very useful from infinite scrolling)

You might be familiar with OrganizationInvitation that already exists in our codebase.

Both UserOrganizationInvitation and OrganizationInvitation describe the same entity. The former is from the perspective of a user where the later from the perspective of an organization.

Exposed API

  const {
    isLoaded,
    organizationList, // Same as before
    createOrganization, // Same as before
    setActive, // Same as before
    userInvitations: {
      data,
      count,
      isFetching,
      isLoading,
      isError,
      page,
      pageCount,
      fetchPage,
      fetchNext,
      fetchPrevious,
      hasNextPage,
      hasPreviousPage,
    },
  } = useOrganizationList({
    userInvitations: {
      page: 1,
      pageSize: 10,
      infinite: true, // Aggregate the data or not. Ideal for infinite lists
      keepPreviousData: true,
    },
  });

@changeset-bot

changeset-bot Bot commented Jul 25, 2023

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 34484b6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@clerk/clerk-js Minor
@clerk/shared Minor
@clerk/clerk-react Minor
@clerk/types Minor
@clerk/chrome-extension Patch
@clerk/clerk-expo Patch
@clerk/remix Patch
gatsby-plugin-clerk Patch
@clerk/nextjs Patch
@clerk/backend Patch
@clerk/fastify Patch
@clerk/localizations Patch
@clerk/clerk-sdk-node Patch

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

@jit-ci jit-ci Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Great news! Jit hasn't found any security issues in your PR. Good Job! 🏆

@panteliselef
panteliselef force-pushed the ORG-59 branch 3 times, most recently from a5c0c58 to 6f059cf Compare July 25, 2023 13:00
Comment thread packages/types/src/api.ts Outdated
Comment thread package-lock.json
Comment thread packages/clerk-js/src/core/resources/UserOrganizationInvitation.ts
type CoreClerkContextWrapperProps = {
clerk: Clerk;
children: React.ReactNode;
swrConfig?: any;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you please explain why we need this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is passed from MockClerkProvider down to CoreOrganizationProvider in order to clear swr cache between tests

Comment thread packages/react/package.json Outdated
Comment thread packages/shared/package.json
<ClientContext.Provider value={clientCtx}>
<SessionContext.Provider value={sessionCtx}>
<OrganizationContext.Provider value={organizationCtx}>
<OrganizationProvider {...organizationCtx.value}>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess we're treating this differently because of swrConfig... Not sure how I feel about this small discrepancy to be honest - if this is a testing-only issue, we might be able to find a different solution that works for the tests. What was the original issue?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need a way to clear cache between tests. The recommended way from the SWR docs is this

Wrapping our CoreClerkContextWrapper with will not work. I verified this by logging the results of useSWRConfig inside our useOrganization while tests where running.

My solution was to expose a OrganizationProvider instead of the "low-level" context and include the <SWRConfig/> provider inside of the OrganizationProvider.
☝️ This is now working as expecting.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see that the cache provider follows the following interface:

interface Cache<Data> {
  get(key: string): Data | undefined
  set(key: string, value: Data): void
  delete(key: string): void
  keys(): IterableIterator<string>
}

Did you try wrapping the test contexts with a SWRConfig that uses a "noop" cache provider, eg:

{
get: () => undefined,
set: noop,
delete: noop,
keys: () => []
}

so that we don't need to reset it between the tests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't see why this would help. It seemed like the useSWRConfig within useOrganization would return the default values, as it couldn't reach the correct provider of SWRConfig.

For example is I was passing <SWRConfig value={{ dedupingInterval: 0 }}/> the useSWRConfig would return {dedupingInterval: 2000} which is the default value

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this mean that a SWRConfig provider is found deeper in the tree so you cannot override the defaults in the tests?

Comment thread packages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment thread packages/shared/src/hooks/useOrganizationList.tsx Outdated
}));
}

function cacheKey(type: 'userInvitations', user: UserResource, pagination: GetUserOrganizationInvitations) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we care about typing these objects here? Do you think this would be enough? If it's generic enough in can be used in other similar places as well - other hooks using swr for example.

Suggested change
function cacheKey(type: 'userInvitations', user: UserResource, pagination: GetUserOrganizationInvitations) {
function cacheKey(...keys: Array<string | number | undefined>) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think you are correct. Although swr@2 seems to automatically parse objects as keys, which will make this function obsolete

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We need to make sure to check whether swr uses referential equality checks or if it somehow builds a scalar value by paring the object's values - otherwise we might risk invalidating the cache on every render.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does this answer your question ?

@panteliselef
panteliselef force-pushed the ORG-59 branch 2 times, most recently from bf065dd to 9a215b6 Compare July 26, 2023 13:48
@panteliselef panteliselef changed the title Introduce UserOrganizationInvitation [WIP] UserOrganizationInvitation & useOrganizationList Jul 26, 2023
@clerk-cookie clerk-cookie removed the types label Aug 3, 2023
@panteliselef panteliselef changed the title [WIP] UserOrganizationInvitation & useOrganizationList ORG-83 UserOrganizationInvitation & useOrganizationList Aug 3, 2023
@panteliselef
panteliselef force-pushed the ORG-59 branch 3 times, most recently from 8d2af63 to 7ce7885 Compare August 4, 2023 18:32
@panteliselef panteliselef changed the title ORG-83 UserOrganizationInvitation & useOrganizationList ORG-83 userInvitations in useOrganizationList Aug 4, 2023
Comment thread packages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment thread packages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment thread packages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment thread packages/shared/src/hooks/useOrganizationList.tsx Outdated
const [paginatedPage, setPaginatedPage] = useState(shouldUseDefaults ? 1 : userInvitations?.initialPage ?? 1);

// Cache initialPage and initialPageSize until unmount
const initialPageRef = useRef(shouldUseDefaults ? 1 : userInvitations?.initialPage ?? 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❓ Do we need the last default fallback? Can't we ensure that the initialPage attribute always has a value?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Follow-up question? Do we need a ref for this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

initialPage does not always have a value as someone can simply pass as params the following

useOrganizationList({infinite:true})

Here we want to not use the defaults in general but use the default value of initialPage

Comment thread packages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment thread packages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment thread packages/shared/src/hooks/useOrganizationList.tsx
return userInvitationsData?.total_count ?? 0;
}, [triggerInfinite, userInvitationsDataInfinite, userInvitationsData]);

const isomorphicIsLoading = triggerInfinite ? userInvitationsLoadingInfinite : userInvitationsLoading;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❓ What does isomorphic mean in this context? It feels that it's a bit confusing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I needed a way to introduce parity between the returned values of useSWR and useSWRInfinite.

based on the params received in the hook return the appropriate values

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The naming still feels a bit off. I'd just remove the isomorphic prefix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, i'll update this shortly in a following PR

setSize,
mutate: userInvitationsInfiniteMutate,
} = useSWRInfinite(getInfiniteKey, ({ initialPage, initialPageSize, status }) => {
return !clerk.loaded || !user

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❓ I believe this hook should trigger the fetch only if triggerInfinite is set to true

@panteliselef panteliselef Aug 8, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It does, you can check getInfiniteKey. The triggering is decided based on the key not the fetcher function. I updated that part and removed the conditional from this place to avoid confusions

@panteliselef panteliselef changed the title ORG-83 userInvitations in useOrganizationList 🚨1️⃣ORG-83 userInvitations in useOrganizationList Aug 9, 2023
Comment thread packages/clerk-js/src/core/resources/UserOrganizationInvitation.ts Outdated
return userInvitationsData?.total_count ?? 0;
}, [triggerInfinite, userInvitationsDataInfinite, userInvitationsData]);

const isomorphicIsLoading = triggerInfinite ? userInvitationsLoadingInfinite : userInvitationsLoading;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The naming still feels a bit off. I'd just remove the isomorphic prefix.

@panteliselef panteliselef changed the title 🚨1️⃣ORG-83 userInvitations in useOrganizationList ORG-83 userInvitations in useOrganizationList Aug 9, 2023
@panteliselef
panteliselef merged commit 4ea30e8 into main Aug 9, 2023
@panteliselef
panteliselef deleted the ORG-59 branch August 9, 2023 12:45
@clerk-cookie clerk-cookie mentioned this pull request Aug 9, 2023
@clerk-cookie

Copy link
Copy Markdown
Collaborator

This PR has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@clerk clerk locked as resolved and limited conversation to collaborators Aug 9, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants