Skip to content

Commit 2705246

Browse files
authored
chore(clerk-js): Use userMemberships instead of organizationList inside OrganizationSwitcher (#2118)
1 parent 12b26ed commit 2705246

File tree

5 files changed

+213
-9
lines changed

5 files changed

+213
-9
lines changed

.changeset/friendly-tables-chew.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
---
4+
5+
Use `userMemberships` instead of `organizationList` inside `<OrganizationSwitcher/>`.

packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserMembershipList.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { OrganizationResource } from '@clerk/types';
22
import React from 'react';
33

4+
import { InfiniteListSpinner } from '../../common';
45
import {
56
useCoreOrganization,
67
useCoreOrganizationList,
@@ -9,25 +10,54 @@ import {
910
} from '../../contexts';
1011
import { Box, descriptors, localizationKeys } from '../../customizables';
1112
import { OrganizationPreview, PersonalWorkspacePreview, PreviewButton } from '../../elements';
13+
import { useInView } from '../../hooks';
1214
import { SwitchArrows } from '../../icons';
1315
import { common } from '../../styledSystem';
16+
import { organizationListParams } from './utils';
1417

1518
export type UserMembershipListProps = {
1619
onPersonalWorkspaceClick: React.MouseEventHandler;
1720
onOrganizationClick: (org: OrganizationResource) => unknown;
1821
};
22+
23+
const useFetchMemberships = () => {
24+
const { userMemberships } = useCoreOrganizationList({
25+
userMemberships: organizationListParams.userMemberships,
26+
});
27+
28+
const { ref } = useInView({
29+
threshold: 0,
30+
onChange: inView => {
31+
if (!inView) {
32+
return;
33+
}
34+
if (userMemberships.hasNextPage) {
35+
userMemberships.fetchNext?.();
36+
}
37+
},
38+
});
39+
40+
return {
41+
userMemberships,
42+
ref,
43+
};
44+
};
1945
export const UserMembershipList = (props: UserMembershipListProps) => {
2046
const { onPersonalWorkspaceClick, onOrganizationClick } = props;
2147

2248
const { hidePersonal } = useOrganizationSwitcherContext();
2349
const { organization: currentOrg } = useCoreOrganization();
24-
const { organizationList } = useCoreOrganizationList();
50+
const { ref, userMemberships } = useFetchMemberships();
2551
const user = useCoreUser();
2652

27-
const otherOrgs = (organizationList || []).map(e => e.organization).filter(o => o.id !== currentOrg?.id);
53+
const otherOrgs = ((userMemberships.count || 0) > 0 ? userMemberships.data || [] : [])
54+
.map(e => e.organization)
55+
.filter(o => o.id !== currentOrg?.id);
2856

2957
const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = user;
3058

59+
const { isLoading, hasNextPage } = userMemberships;
60+
3161
return (
3262
<Box
3363
sx={t => ({
@@ -71,6 +101,7 @@ export const UserMembershipList = (props: UserMembershipListProps) => {
71101
/>
72102
</PreviewButton>
73103
))}
104+
{(hasNextPage || isLoading) && <InfiniteListSpinner ref={ref} />}
74105
</Box>
75106
);
76107
};

packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import { describe } from '@jest/globals';
44
import { act, render, runFakeTimers, waitFor } from '../../../../testUtils';
55
import { bindCreateFixtures } from '../../../utils/test/createFixtures';
66
import { OrganizationSwitcher } from '../OrganizationSwitcher';
7-
import { createFakeUserOrganizationInvitation, createFakeUserOrganizationSuggestion } from './utlis';
7+
import {
8+
createFakeUserOrganizationInvitation,
9+
createFakeUserOrganizationMembership,
10+
createFakeUserOrganizationSuggestion,
11+
} from './utlis';
812

913
const { createFixtures } = bindCreateFixtures('OrganizationSwitcher');
1014

@@ -130,11 +134,43 @@ describe('OrganizationSwitcher', () => {
130134
});
131135

132136
it('lists all organizations the user belongs to', async () => {
133-
const { wrapper, props } = await createFixtures(f => {
137+
const { wrapper, props, fixtures } = await createFixtures(f => {
134138
f.withOrganizations();
135139
f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: ['Org1', 'Org2'] });
136140
});
137141

142+
fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
143+
Promise.resolve({
144+
data: [
145+
createFakeUserOrganizationMembership({
146+
id: '1',
147+
organization: {
148+
id: '1',
149+
name: 'Org1',
150+
slug: 'org1',
151+
membersCount: 1,
152+
adminDeleteEnabled: false,
153+
maxAllowedMemberships: 1,
154+
pendingInvitationsCount: 1,
155+
},
156+
}),
157+
createFakeUserOrganizationMembership({
158+
id: '2',
159+
organization: {
160+
id: '2',
161+
name: 'Org2',
162+
slug: 'org2',
163+
membersCount: 1,
164+
adminDeleteEnabled: false,
165+
maxAllowedMemberships: 1,
166+
pendingInvitationsCount: 1,
167+
},
168+
}),
169+
],
170+
total_count: 2,
171+
}),
172+
);
173+
138174
props.setProps({ hidePersonal: false });
139175
const { getAllByText, getByText, getByRole, userEvent } = render(<OrganizationSwitcher />, { wrapper });
140176
await userEvent.click(getByRole('button'));
@@ -313,6 +349,39 @@ describe('OrganizationSwitcher', () => {
313349
create_organization_enabled: false,
314350
});
315351
});
352+
353+
fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
354+
Promise.resolve({
355+
data: [
356+
createFakeUserOrganizationMembership({
357+
id: '1',
358+
organization: {
359+
id: '1',
360+
name: 'Org1',
361+
slug: 'org1',
362+
membersCount: 1,
363+
adminDeleteEnabled: false,
364+
maxAllowedMemberships: 1,
365+
pendingInvitationsCount: 1,
366+
},
367+
}),
368+
createFakeUserOrganizationMembership({
369+
id: '2',
370+
organization: {
371+
id: '2',
372+
name: 'Org2',
373+
slug: 'org2',
374+
membersCount: 1,
375+
adminDeleteEnabled: false,
376+
maxAllowedMemberships: 1,
377+
pendingInvitationsCount: 1,
378+
},
379+
}),
380+
],
381+
total_count: 2,
382+
}),
383+
);
384+
316385
fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
317386

318387
props.setProps({ hidePersonal: true });

packages/clerk-js/src/ui/components/OrganizationSwitcher/UserMembershipList.tsx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { OrganizationResource } from '@clerk/types';
22
import React from 'react';
33

4+
import { InfiniteListSpinner } from '../../common';
45
import {
56
useCoreOrganization,
67
useCoreOrganizationList,
@@ -9,26 +10,54 @@ import {
910
} from '../../contexts';
1011
import { Box, descriptors, localizationKeys } from '../../customizables';
1112
import { OrganizationPreview, PersonalWorkspacePreview, PreviewButton } from '../../elements';
13+
import { useInView } from '../../hooks';
1214
import { SwitchArrows } from '../../icons';
1315
import { common } from '../../styledSystem';
16+
import { organizationListParams } from './utils';
1417

1518
export type UserMembershipListProps = {
1619
onPersonalWorkspaceClick: React.MouseEventHandler;
1720
onOrganizationClick: (org: OrganizationResource) => unknown;
1821
};
22+
23+
const useFetchMemberships = () => {
24+
const { userMemberships } = useCoreOrganizationList({
25+
userMemberships: organizationListParams.userMemberships,
26+
});
27+
28+
const { ref } = useInView({
29+
threshold: 0,
30+
onChange: inView => {
31+
if (!inView) {
32+
return;
33+
}
34+
if (userMemberships.hasNextPage) {
35+
userMemberships.fetchNext?.();
36+
}
37+
},
38+
});
39+
40+
return {
41+
userMemberships,
42+
ref,
43+
};
44+
};
1945
export const UserMembershipList = (props: UserMembershipListProps) => {
2046
const { onPersonalWorkspaceClick, onOrganizationClick } = props;
2147

2248
const { hidePersonal } = useOrganizationSwitcherContext();
2349
const { organization: currentOrg } = useCoreOrganization();
24-
const { organizationList } = useCoreOrganizationList();
50+
const { ref, userMemberships } = useFetchMemberships();
2551
const user = useCoreUser();
2652

27-
const otherOrgs = (organizationList || []).map(e => e.organization).filter(o => o.id !== currentOrg?.id);
53+
const otherOrgs = ((userMemberships.count || 0) > 0 ? userMemberships.data || [] : [])
54+
.map(e => e.organization)
55+
.filter(o => o.id !== currentOrg?.id);
2856

29-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3057
const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = user;
3158

59+
const { isLoading, hasNextPage } = userMemberships;
60+
3261
return (
3362
<Box
3463
sx={t => ({
@@ -72,6 +101,7 @@ export const UserMembershipList = (props: UserMembershipListProps) => {
72101
/>
73102
</PreviewButton>
74103
))}
104+
{(hasNextPage || isLoading) && <InfiniteListSpinner ref={ref} />}
75105
</Box>
76106
);
77107
};

packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import { describe } from '@jest/globals';
44
import { act, render, runFakeTimers, waitFor } from '../../../../testUtils';
55
import { bindCreateFixtures } from '../../../utils/test/createFixtures';
66
import { OrganizationSwitcher } from '../OrganizationSwitcher';
7-
import { createFakeUserOrganizationInvitation, createFakeUserOrganizationSuggestion } from './utlis';
7+
import {
8+
createFakeUserOrganizationInvitation,
9+
createFakeUserOrganizationMembership,
10+
createFakeUserOrganizationSuggestion,
11+
} from './utlis';
812

913
const { createFixtures } = bindCreateFixtures('OrganizationSwitcher');
1014

@@ -130,11 +134,43 @@ describe('OrganizationSwitcher', () => {
130134
});
131135

132136
it('lists all organizations the user belongs to', async () => {
133-
const { wrapper, props } = await createFixtures(f => {
137+
const { wrapper, props, fixtures } = await createFixtures(f => {
134138
f.withOrganizations();
135139
f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: ['Org1', 'Org2'] });
136140
});
137141

142+
fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
143+
Promise.resolve({
144+
data: [
145+
createFakeUserOrganizationMembership({
146+
id: '1',
147+
organization: {
148+
id: '1',
149+
name: 'Org1',
150+
slug: 'org1',
151+
membersCount: 1,
152+
adminDeleteEnabled: false,
153+
maxAllowedMemberships: 1,
154+
pendingInvitationsCount: 1,
155+
},
156+
}),
157+
createFakeUserOrganizationMembership({
158+
id: '2',
159+
organization: {
160+
id: '2',
161+
name: 'Org2',
162+
slug: 'org2',
163+
membersCount: 1,
164+
adminDeleteEnabled: false,
165+
maxAllowedMemberships: 1,
166+
pendingInvitationsCount: 1,
167+
},
168+
}),
169+
],
170+
total_count: 2,
171+
}),
172+
);
173+
138174
props.setProps({ hidePersonal: false });
139175
const { getAllByText, getByText, getByRole, userEvent } = render(<OrganizationSwitcher />, { wrapper });
140176
await userEvent.click(getByRole('button'));
@@ -313,6 +349,39 @@ describe('OrganizationSwitcher', () => {
313349
create_organization_enabled: false,
314350
});
315351
});
352+
353+
fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce(
354+
Promise.resolve({
355+
data: [
356+
createFakeUserOrganizationMembership({
357+
id: '1',
358+
organization: {
359+
id: '1',
360+
name: 'Org1',
361+
slug: 'org1',
362+
membersCount: 1,
363+
adminDeleteEnabled: false,
364+
maxAllowedMemberships: 1,
365+
pendingInvitationsCount: 1,
366+
},
367+
}),
368+
createFakeUserOrganizationMembership({
369+
id: '2',
370+
organization: {
371+
id: '2',
372+
name: 'Org2',
373+
slug: 'org2',
374+
membersCount: 1,
375+
adminDeleteEnabled: false,
376+
maxAllowedMemberships: 1,
377+
pendingInvitationsCount: 1,
378+
},
379+
}),
380+
],
381+
total_count: 2,
382+
}),
383+
);
384+
316385
fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve());
317386

318387
props.setProps({ hidePersonal: true });

0 commit comments

Comments
 (0)