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

@panteliselefpanteliselef 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 beforeuserInvitations: {
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 listskeepPreviousData: true,},});

@changeset-bot

changeset-botBot 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
NameType
@clerk/clerk-jsMinor
@clerk/sharedMinor
@clerk/clerk-reactMinor
@clerk/typesMinor
@clerk/chrome-extensionPatch
@clerk/clerk-expoPatch
@clerk/remixPatch
gatsby-plugin-clerkPatch
@clerk/nextjsPatch
@clerk/backendPatch
@clerk/fastifyPatch
@clerk/localizationsPatch
@clerk/clerk-sdk-nodePatch

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-cijit-ciBot 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
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from a5c0c58 to 6f059cfCompareJuly 25, 2023 13:00
Comment threadpackages/types/src/api.ts Outdated
Comment threadpackage-lock.json
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
ContributorAuthor

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 threadpackages/react/package.json Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
functioncacheKey(type: 'userInvitations',user: UserResource,pagination: GetUserOrganizationInvitations){
functioncacheKey(...keys: Array<string|number|undefined>){

Copy link
Copy Markdown
ContributorAuthor

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
ContributorAuthor

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
panteliselefforce-pushed the ORG-59 branch 2 times, most recently from bf065dd to 9a215b6CompareJuly 26, 2023 13:48
@panteliselefpanteliselef changed the title Introduce UserOrganizationInvitation[WIP] UserOrganizationInvitation & useOrganizationListJul 26, 2023
@panteliselefpanteliselef changed the title [WIP] UserOrganizationInvitation & useOrganizationListORG-83 UserOrganizationInvitation & useOrganizationListAug 3, 2023
@panteliselef
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from 8d2af63 to 7ce7885CompareAugust 4, 2023 18:32
@panteliselefpanteliselef changed the title ORG-83 UserOrganizationInvitation & useOrganizationListORG-83 userInvitations in useOrganizationListAug 4, 2023
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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

@panteliselefpanteliselefAug 8, 2023

Copy link
Copy Markdown
ContributorAuthor

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

@panteliselefpanteliselef changed the title ORG-83 userInvitations in useOrganizationList🚨1️⃣ORG-83 userInvitations in useOrganizationListAug 9, 2023
Comment threadpackages/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.

@panteliselefpanteliselef changed the title 🚨1️⃣ORG-83 userInvitations in useOrganizationListORG-83 userInvitations in useOrganizationListAug 9, 2023
@panteliselef
panteliselef merged commit 4ea30e8 into mainAug 9, 2023
@panteliselef
panteliselef deleted the ORG-59 branch August 9, 2023 12:45
@clerk-cookieclerk-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.

@clerkclerk locked as resolved and limited conversation to collaborators Aug 9, 2024
Sign up for freeto 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

@panteliselef@clerk-cookie@SokratisVidros@nikosdouvlis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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

@panteliselefpanteliselef 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 beforeuserInvitations: {
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 listskeepPreviousData: true,},});

@changeset-bot

changeset-botBot 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
NameType
@clerk/clerk-jsMinor
@clerk/sharedMinor
@clerk/clerk-reactMinor
@clerk/typesMinor
@clerk/chrome-extensionPatch
@clerk/clerk-expoPatch
@clerk/remixPatch
gatsby-plugin-clerkPatch
@clerk/nextjsPatch
@clerk/backendPatch
@clerk/fastifyPatch
@clerk/localizationsPatch
@clerk/clerk-sdk-nodePatch

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-cijit-ciBot 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
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from a5c0c58 to 6f059cfCompareJuly 25, 2023 13:00
Comment threadpackages/types/src/api.ts Outdated
Comment threadpackage-lock.json
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
ContributorAuthor

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 threadpackages/react/package.json Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
functioncacheKey(type: 'userInvitations',user: UserResource,pagination: GetUserOrganizationInvitations){
functioncacheKey(...keys: Array<string|number|undefined>){

Copy link
Copy Markdown
ContributorAuthor

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
ContributorAuthor

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
panteliselefforce-pushed the ORG-59 branch 2 times, most recently from bf065dd to 9a215b6CompareJuly 26, 2023 13:48
@panteliselefpanteliselef changed the title Introduce UserOrganizationInvitation[WIP] UserOrganizationInvitation & useOrganizationListJul 26, 2023
@panteliselefpanteliselef changed the title [WIP] UserOrganizationInvitation & useOrganizationListORG-83 UserOrganizationInvitation & useOrganizationListAug 3, 2023
@panteliselef
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from 8d2af63 to 7ce7885CompareAugust 4, 2023 18:32
@panteliselefpanteliselef changed the title ORG-83 UserOrganizationInvitation & useOrganizationListORG-83 userInvitations in useOrganizationListAug 4, 2023
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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

@panteliselefpanteliselefAug 8, 2023

Copy link
Copy Markdown
ContributorAuthor

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

@panteliselefpanteliselef changed the title ORG-83 userInvitations in useOrganizationList🚨1️⃣ORG-83 userInvitations in useOrganizationListAug 9, 2023
Comment threadpackages/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.

@panteliselefpanteliselef changed the title 🚨1️⃣ORG-83 userInvitations in useOrganizationListORG-83 userInvitations in useOrganizationListAug 9, 2023
@panteliselef
panteliselef merged commit 4ea30e8 into mainAug 9, 2023
@panteliselef
panteliselef deleted the ORG-59 branch August 9, 2023 12:45
@clerk-cookieclerk-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.

@clerkclerk locked as resolved and limited conversation to collaborators Aug 9, 2024
Sign up for freeto 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

@panteliselef@clerk-cookie@SokratisVidros@nikosdouvlis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@panteliselefpanteliselef 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 beforeuserInvitations: {
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 listskeepPreviousData: true,},});

@changeset-bot

changeset-botBot 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
NameType
@clerk/clerk-jsMinor
@clerk/sharedMinor
@clerk/clerk-reactMinor
@clerk/typesMinor
@clerk/chrome-extensionPatch
@clerk/clerk-expoPatch
@clerk/remixPatch
gatsby-plugin-clerkPatch
@clerk/nextjsPatch
@clerk/backendPatch
@clerk/fastifyPatch
@clerk/localizationsPatch
@clerk/clerk-sdk-nodePatch

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-cijit-ciBot 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
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from a5c0c58 to 6f059cfCompareJuly 25, 2023 13:00
Comment threadpackages/types/src/api.ts Outdated
Comment threadpackage-lock.json
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
ContributorAuthor

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 threadpackages/react/package.json Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
functioncacheKey(type: 'userInvitations',user: UserResource,pagination: GetUserOrganizationInvitations){
functioncacheKey(...keys: Array<string|number|undefined>){

Copy link
Copy Markdown
ContributorAuthor

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
ContributorAuthor

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
panteliselefforce-pushed the ORG-59 branch 2 times, most recently from bf065dd to 9a215b6CompareJuly 26, 2023 13:48
@panteliselefpanteliselef changed the title Introduce UserOrganizationInvitation[WIP] UserOrganizationInvitation & useOrganizationListJul 26, 2023
@panteliselefpanteliselef changed the title [WIP] UserOrganizationInvitation & useOrganizationListORG-83 UserOrganizationInvitation & useOrganizationListAug 3, 2023
@panteliselef
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from 8d2af63 to 7ce7885CompareAugust 4, 2023 18:32
@panteliselefpanteliselef changed the title ORG-83 UserOrganizationInvitation & useOrganizationListORG-83 userInvitations in useOrganizationListAug 4, 2023
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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

@panteliselefpanteliselefAug 8, 2023

Copy link
Copy Markdown
ContributorAuthor

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

@panteliselefpanteliselef changed the title ORG-83 userInvitations in useOrganizationList🚨1️⃣ORG-83 userInvitations in useOrganizationListAug 9, 2023
Comment threadpackages/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.

@panteliselefpanteliselef changed the title 🚨1️⃣ORG-83 userInvitations in useOrganizationListORG-83 userInvitations in useOrganizationListAug 9, 2023
@panteliselef
panteliselef merged commit 4ea30e8 into mainAug 9, 2023
@panteliselef
panteliselef deleted the ORG-59 branch August 9, 2023 12:45
@clerk-cookieclerk-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.

@clerkclerk locked as resolved and limited conversation to collaborators Aug 9, 2024
Sign up for freeto 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

@panteliselef@clerk-cookie@SokratisVidros@nikosdouvlis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@panteliselefpanteliselef 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 beforeuserInvitations: {
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 listskeepPreviousData: true,},});

@changeset-bot

changeset-botBot 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
NameType
@clerk/clerk-jsMinor
@clerk/sharedMinor
@clerk/clerk-reactMinor
@clerk/typesMinor
@clerk/chrome-extensionPatch
@clerk/clerk-expoPatch
@clerk/remixPatch
gatsby-plugin-clerkPatch
@clerk/nextjsPatch
@clerk/backendPatch
@clerk/fastifyPatch
@clerk/localizationsPatch
@clerk/clerk-sdk-nodePatch

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-cijit-ciBot 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
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from a5c0c58 to 6f059cfCompareJuly 25, 2023 13:00
Comment threadpackages/types/src/api.ts Outdated
Comment threadpackage-lock.json
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
ContributorAuthor

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 threadpackages/react/package.json Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
functioncacheKey(type: 'userInvitations',user: UserResource,pagination: GetUserOrganizationInvitations){
functioncacheKey(...keys: Array<string|number|undefined>){

Copy link
Copy Markdown
ContributorAuthor

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
ContributorAuthor

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
panteliselefforce-pushed the ORG-59 branch 2 times, most recently from bf065dd to 9a215b6CompareJuly 26, 2023 13:48
@panteliselefpanteliselef changed the title Introduce UserOrganizationInvitation[WIP] UserOrganizationInvitation & useOrganizationListJul 26, 2023
@panteliselefpanteliselef changed the title [WIP] UserOrganizationInvitation & useOrganizationListORG-83 UserOrganizationInvitation & useOrganizationListAug 3, 2023
@panteliselef
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from 8d2af63 to 7ce7885CompareAugust 4, 2023 18:32
@panteliselefpanteliselef changed the title ORG-83 UserOrganizationInvitation & useOrganizationListORG-83 userInvitations in useOrganizationListAug 4, 2023
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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

@panteliselefpanteliselefAug 8, 2023

Copy link
Copy Markdown
ContributorAuthor

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

@panteliselefpanteliselef changed the title ORG-83 userInvitations in useOrganizationList🚨1️⃣ORG-83 userInvitations in useOrganizationListAug 9, 2023
Comment threadpackages/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.

@panteliselefpanteliselef changed the title 🚨1️⃣ORG-83 userInvitations in useOrganizationListORG-83 userInvitations in useOrganizationListAug 9, 2023
@panteliselef
panteliselef merged commit 4ea30e8 into mainAug 9, 2023
@panteliselef
panteliselef deleted the ORG-59 branch August 9, 2023 12:45
@clerk-cookieclerk-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.

@clerkclerk locked as resolved and limited conversation to collaborators Aug 9, 2024
Sign up for freeto 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

@panteliselef@clerk-cookie@SokratisVidros@nikosdouvlis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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

@panteliselefpanteliselef 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 beforeuserInvitations: {
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 listskeepPreviousData: true,},});

@changeset-bot

changeset-botBot 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
NameType
@clerk/clerk-jsMinor
@clerk/sharedMinor
@clerk/clerk-reactMinor
@clerk/typesMinor
@clerk/chrome-extensionPatch
@clerk/clerk-expoPatch
@clerk/remixPatch
gatsby-plugin-clerkPatch
@clerk/nextjsPatch
@clerk/backendPatch
@clerk/fastifyPatch
@clerk/localizationsPatch
@clerk/clerk-sdk-nodePatch

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-cijit-ciBot 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
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from a5c0c58 to 6f059cfCompareJuly 25, 2023 13:00
Comment threadpackages/types/src/api.ts Outdated
Comment threadpackage-lock.json
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
ContributorAuthor

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 threadpackages/react/package.json Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
functioncacheKey(type: 'userInvitations',user: UserResource,pagination: GetUserOrganizationInvitations){
functioncacheKey(...keys: Array<string|number|undefined>){

Copy link
Copy Markdown
ContributorAuthor

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
ContributorAuthor

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
panteliselefforce-pushed the ORG-59 branch 2 times, most recently from bf065dd to 9a215b6CompareJuly 26, 2023 13:48
@panteliselefpanteliselef changed the title Introduce UserOrganizationInvitation[WIP] UserOrganizationInvitation & useOrganizationListJul 26, 2023
@panteliselefpanteliselef changed the title [WIP] UserOrganizationInvitation & useOrganizationListORG-83 UserOrganizationInvitation & useOrganizationListAug 3, 2023
@panteliselef
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from 8d2af63 to 7ce7885CompareAugust 4, 2023 18:32
@panteliselefpanteliselef changed the title ORG-83 UserOrganizationInvitation & useOrganizationListORG-83 userInvitations in useOrganizationListAug 4, 2023
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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

@panteliselefpanteliselefAug 8, 2023

Copy link
Copy Markdown
ContributorAuthor

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

@panteliselefpanteliselef changed the title ORG-83 userInvitations in useOrganizationList🚨1️⃣ORG-83 userInvitations in useOrganizationListAug 9, 2023
Comment threadpackages/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.

@panteliselefpanteliselef changed the title 🚨1️⃣ORG-83 userInvitations in useOrganizationListORG-83 userInvitations in useOrganizationListAug 9, 2023
@panteliselef
panteliselef merged commit 4ea30e8 into mainAug 9, 2023
@panteliselef
panteliselef deleted the ORG-59 branch August 9, 2023 12:45
@clerk-cookieclerk-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.

@clerkclerk locked as resolved and limited conversation to collaborators Aug 9, 2024
Sign up for freeto 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

@panteliselef@clerk-cookie@SokratisVidros@nikosdouvlis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@panteliselefpanteliselef 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 beforeuserInvitations: {
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 listskeepPreviousData: true,},});

@changeset-bot

changeset-botBot 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
NameType
@clerk/clerk-jsMinor
@clerk/sharedMinor
@clerk/clerk-reactMinor
@clerk/typesMinor
@clerk/chrome-extensionPatch
@clerk/clerk-expoPatch
@clerk/remixPatch
gatsby-plugin-clerkPatch
@clerk/nextjsPatch
@clerk/backendPatch
@clerk/fastifyPatch
@clerk/localizationsPatch
@clerk/clerk-sdk-nodePatch

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-cijit-ciBot 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
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from a5c0c58 to 6f059cfCompareJuly 25, 2023 13:00
Comment threadpackages/types/src/api.ts Outdated
Comment threadpackage-lock.json
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
ContributorAuthor

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 threadpackages/react/package.json Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
functioncacheKey(type: 'userInvitations',user: UserResource,pagination: GetUserOrganizationInvitations){
functioncacheKey(...keys: Array<string|number|undefined>){

Copy link
Copy Markdown
ContributorAuthor

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
ContributorAuthor

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
panteliselefforce-pushed the ORG-59 branch 2 times, most recently from bf065dd to 9a215b6CompareJuly 26, 2023 13:48
@panteliselefpanteliselef changed the title Introduce UserOrganizationInvitation[WIP] UserOrganizationInvitation & useOrganizationListJul 26, 2023
@panteliselefpanteliselef changed the title [WIP] UserOrganizationInvitation & useOrganizationListORG-83 UserOrganizationInvitation & useOrganizationListAug 3, 2023
@panteliselef
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from 8d2af63 to 7ce7885CompareAugust 4, 2023 18:32
@panteliselefpanteliselef changed the title ORG-83 UserOrganizationInvitation & useOrganizationListORG-83 userInvitations in useOrganizationListAug 4, 2023
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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

@panteliselefpanteliselefAug 8, 2023

Copy link
Copy Markdown
ContributorAuthor

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

@panteliselefpanteliselef changed the title ORG-83 userInvitations in useOrganizationList🚨1️⃣ORG-83 userInvitations in useOrganizationListAug 9, 2023
Comment threadpackages/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.

@panteliselefpanteliselef changed the title 🚨1️⃣ORG-83 userInvitations in useOrganizationListORG-83 userInvitations in useOrganizationListAug 9, 2023
@panteliselef
panteliselef merged commit 4ea30e8 into mainAug 9, 2023
@panteliselef
panteliselef deleted the ORG-59 branch August 9, 2023 12:45
@clerk-cookieclerk-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.

@clerkclerk locked as resolved and limited conversation to collaborators Aug 9, 2024
Sign up for freeto 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

@panteliselef@clerk-cookie@SokratisVidros@nikosdouvlis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@panteliselefpanteliselef 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 beforeuserInvitations: {
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 listskeepPreviousData: true,},});

@changeset-bot

changeset-botBot 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
NameType
@clerk/clerk-jsMinor
@clerk/sharedMinor
@clerk/clerk-reactMinor
@clerk/typesMinor
@clerk/chrome-extensionPatch
@clerk/clerk-expoPatch
@clerk/remixPatch
gatsby-plugin-clerkPatch
@clerk/nextjsPatch
@clerk/backendPatch
@clerk/fastifyPatch
@clerk/localizationsPatch
@clerk/clerk-sdk-nodePatch

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-cijit-ciBot 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
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from a5c0c58 to 6f059cfCompareJuly 25, 2023 13:00
Comment threadpackages/types/src/api.ts Outdated
Comment threadpackage-lock.json
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
ContributorAuthor

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 threadpackages/react/package.json Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
functioncacheKey(type: 'userInvitations',user: UserResource,pagination: GetUserOrganizationInvitations){
functioncacheKey(...keys: Array<string|number|undefined>){

Copy link
Copy Markdown
ContributorAuthor

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
ContributorAuthor

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
panteliselefforce-pushed the ORG-59 branch 2 times, most recently from bf065dd to 9a215b6CompareJuly 26, 2023 13:48
@panteliselefpanteliselef changed the title Introduce UserOrganizationInvitation[WIP] UserOrganizationInvitation & useOrganizationListJul 26, 2023
@panteliselefpanteliselef changed the title [WIP] UserOrganizationInvitation & useOrganizationListORG-83 UserOrganizationInvitation & useOrganizationListAug 3, 2023
@panteliselef
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from 8d2af63 to 7ce7885CompareAugust 4, 2023 18:32
@panteliselefpanteliselef changed the title ORG-83 UserOrganizationInvitation & useOrganizationListORG-83 userInvitations in useOrganizationListAug 4, 2023
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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

@panteliselefpanteliselefAug 8, 2023

Copy link
Copy Markdown
ContributorAuthor

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

@panteliselefpanteliselef changed the title ORG-83 userInvitations in useOrganizationList🚨1️⃣ORG-83 userInvitations in useOrganizationListAug 9, 2023
Comment threadpackages/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.

@panteliselefpanteliselef changed the title 🚨1️⃣ORG-83 userInvitations in useOrganizationListORG-83 userInvitations in useOrganizationListAug 9, 2023
@panteliselef
panteliselef merged commit 4ea30e8 into mainAug 9, 2023
@panteliselef
panteliselef deleted the ORG-59 branch August 9, 2023 12:45
@clerk-cookieclerk-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.

@clerkclerk locked as resolved and limited conversation to collaborators Aug 9, 2024
Sign up for freeto 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

@panteliselef@clerk-cookie@SokratisVidros@nikosdouvlis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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

@panteliselefpanteliselef 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 beforeuserInvitations: {
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 listskeepPreviousData: true,},});

@changeset-bot

changeset-botBot 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
NameType
@clerk/clerk-jsMinor
@clerk/sharedMinor
@clerk/clerk-reactMinor
@clerk/typesMinor
@clerk/chrome-extensionPatch
@clerk/clerk-expoPatch
@clerk/remixPatch
gatsby-plugin-clerkPatch
@clerk/nextjsPatch
@clerk/backendPatch
@clerk/fastifyPatch
@clerk/localizationsPatch
@clerk/clerk-sdk-nodePatch

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-cijit-ciBot 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
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from a5c0c58 to 6f059cfCompareJuly 25, 2023 13:00
Comment threadpackages/types/src/api.ts Outdated
Comment threadpackage-lock.json
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
ContributorAuthor

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 threadpackages/react/package.json Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
functioncacheKey(type: 'userInvitations',user: UserResource,pagination: GetUserOrganizationInvitations){
functioncacheKey(...keys: Array<string|number|undefined>){

Copy link
Copy Markdown
ContributorAuthor

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
ContributorAuthor

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
panteliselefforce-pushed the ORG-59 branch 2 times, most recently from bf065dd to 9a215b6CompareJuly 26, 2023 13:48
@panteliselefpanteliselef changed the title Introduce UserOrganizationInvitation[WIP] UserOrganizationInvitation & useOrganizationListJul 26, 2023
@panteliselefpanteliselef changed the title [WIP] UserOrganizationInvitation & useOrganizationListORG-83 UserOrganizationInvitation & useOrganizationListAug 3, 2023
@panteliselef
panteliselefforce-pushed the ORG-59 branch 3 times, most recently from 8d2af63 to 7ce7885CompareAugust 4, 2023 18:32
@panteliselefpanteliselef changed the title ORG-83 UserOrganizationInvitation & useOrganizationListORG-83 userInvitations in useOrganizationListAug 4, 2023
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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 threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/shared/src/hooks/useOrganizationList.tsx Outdated
Comment threadpackages/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
ContributorAuthor

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
ContributorAuthor

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

@panteliselefpanteliselefAug 8, 2023

Copy link
Copy Markdown
ContributorAuthor

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

@panteliselefpanteliselef changed the title ORG-83 userInvitations in useOrganizationList🚨1️⃣ORG-83 userInvitations in useOrganizationListAug 9, 2023
Comment threadpackages/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.

@panteliselefpanteliselef changed the title 🚨1️⃣ORG-83 userInvitations in useOrganizationListORG-83 userInvitations in useOrganizationListAug 9, 2023
@panteliselef
panteliselef merged commit 4ea30e8 into mainAug 9, 2023
@panteliselef
panteliselef deleted the ORG-59 branch August 9, 2023 12:45
@clerk-cookieclerk-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.

@clerkclerk locked as resolved and limited conversation to collaborators Aug 9, 2024
Sign up for freeto 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

@panteliselef@clerk-cookie@SokratisVidros@nikosdouvlis