Skip to content

Commit 79cf5dd

Browse files
committed
Replace HydrateFallback with Suspense in team setting tabs
Using await in clientLoader causes navigating between the tabs to do nothing until the clientLoader promise resolves. Removing the await forces the component to deal with the promises returned by the clientLoader, for which Suspense+Await components is a good pattern. Using it rendes HydarteFallback obsolete. All tabs use all-or-nothing rendering policy. For profile tab, only a form is rendered and it requires the resolved values. For Members and Service Account tabs we could consider rendering the sidebar and empty table initially, but I though it would be awkward for the add button to pop on the sidebar once the download finished (or alternatively a skeleton component to disappear). For Settings tab partial rendering could be considered, but that tab doesn't have an API endpoint available yet and shows only dummy data. On the other hand, for consistency, all-or-nothing strategy could be used there as well.
1 parent edf6b31 commit 79cf5dd

File tree

4 files changed

+155
-122
lines changed

4 files changed

+155
-122
lines changed
Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { useLoaderData, useOutletContext, useRevalidator } from "react-router";
1+
import { Suspense } from "react";
2+
import {
3+
Await,
4+
useLoaderData,
5+
useOutletContext,
6+
useRevalidator,
7+
} from "react-router";
28

39
import { type OutletContextShape } from "app/root";
410
import { makeTeamSettingsTabLoader } from "cyberstorm/utils/dapperClientLoaders";
@@ -8,14 +14,10 @@ import "./Members.css";
814

915
export const clientLoader = makeTeamSettingsTabLoader(
1016
async (dapper, teamName) => ({
11-
members: await dapper.getTeamMembers(teamName),
17+
members: dapper.getTeamMembers(teamName),
1218
})
1319
);
1420

15-
export function HydrateFallback() {
16-
return <div style={{ padding: "32px" }}>Loading...</div>;
17-
}
18-
1921
export default function Members() {
2022
const { teamName, members } = useLoaderData<typeof clientLoader>();
2123
const outletContext = useOutletContext() as OutletContextShape;
@@ -26,26 +28,32 @@ export default function Members() {
2628
}
2729

2830
return (
29-
<div className="settings-items">
30-
<div className="settings-items__item">
31-
<div className="settings-items__meta">
32-
<p className="settings-items__title">Teams</p>
33-
<p className="settings-items__description">Manage your teams</p>
34-
<MemberAddForm
35-
teamName={teamName}
36-
updateTrigger={teamMemberRevalidate}
37-
config={outletContext.requestConfig}
38-
/>
39-
</div>
40-
<div className="settings-items__content">
41-
<MembersTable
42-
teamName={teamName}
43-
members={members}
44-
updateTrigger={teamMemberRevalidate}
45-
config={outletContext.requestConfig}
46-
/>
47-
</div>
48-
</div>
49-
</div>
31+
<Suspense fallback={<div>Loading...</div>}>
32+
<Await resolve={members}>
33+
{(resolvedMembers) => (
34+
<div className="settings-items">
35+
<div className="settings-items__item">
36+
<div className="settings-items__meta">
37+
<p className="settings-items__title">Teams</p>
38+
<p className="settings-items__description">Manage your teams</p>
39+
<MemberAddForm
40+
teamName={teamName}
41+
updateTrigger={teamMemberRevalidate}
42+
config={outletContext.requestConfig}
43+
/>
44+
</div>
45+
<div className="settings-items__content">
46+
<MembersTable
47+
teamName={teamName}
48+
members={resolvedMembers}
49+
updateTrigger={teamMemberRevalidate}
50+
config={outletContext.requestConfig}
51+
/>
52+
</div>
53+
</div>
54+
</div>
55+
)}
56+
</Await>
57+
</Suspense>
5058
);
5159
}

apps/cyberstorm-remix/app/settings/teams/team/tabs/Profile/Profile.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
import { useReducer } from "react";
2-
import { useLoaderData, useOutletContext, useRevalidator } from "react-router";
1+
import { Suspense, useReducer } from "react";
2+
import {
3+
Await,
4+
useLoaderData,
5+
useOutletContext,
6+
useRevalidator,
7+
} from "react-router";
38

49
import { NewButton, NewTextInput, useToast } from "@thunderstore/cyberstorm";
510
import {
@@ -18,21 +23,23 @@ export const clientLoader = makeTeamSettingsTabLoader(
1823
// TODO: for hygienie we shouldn't use this public endpoint but
1924
// have an endpoint that confirms user permissions and returns
2025
// possibly sensitive information.
21-
team: await dapper.getTeamDetails(teamName),
26+
team: dapper.getTeamDetails(teamName),
2227
})
2328
);
2429

25-
export function HydrateFallback() {
26-
return <div style={{ padding: "32px" }}>Loading...</div>;
27-
}
28-
2930
export default function Profile() {
3031
const { team } = useLoaderData<typeof clientLoader>();
3132

3233
return (
33-
<div className="settings-items team-profile">
34-
<ProfileForm team={team} />
35-
</div>
34+
<Suspense fallback={<div>Loading...</div>}>
35+
<Await resolve={team}>
36+
{(resolvedTeam) => (
37+
<div className="settings-items team-profile">
38+
<ProfileForm team={resolvedTeam} />
39+
</div>
40+
)}
41+
</Await>
42+
</Suspense>
3643
);
3744
}
3845

apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { faPlus } from "@fortawesome/free-solid-svg-icons";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3-
import { useReducer, useState } from "react";
4-
import { useLoaderData, useOutletContext, useRevalidator } from "react-router";
3+
import { Suspense, useReducer, useState } from "react";
4+
import {
5+
Await,
6+
useLoaderData,
7+
useOutletContext,
8+
useRevalidator,
9+
} from "react-router";
510

611
import {
712
NewAlert,
@@ -24,14 +29,10 @@ import "./ServiceAccounts.css";
2429

2530
export const clientLoader = makeTeamSettingsTabLoader(
2631
async (dapper, teamName) => ({
27-
serviceAccounts: await dapper.getTeamServiceAccounts(teamName),
32+
serviceAccounts: dapper.getTeamServiceAccounts(teamName),
2833
})
2934
);
3035

31-
export function HydrateFallback() {
32-
return <div style={{ padding: "32px" }}>Loading...</div>;
33-
}
34-
3536
export default function ServiceAccounts() {
3637
const { teamName, serviceAccounts } = useLoaderData<typeof clientLoader>();
3738
const revalidator = useRevalidator();
@@ -41,25 +42,33 @@ export default function ServiceAccounts() {
4142
}
4243

4344
return (
44-
<div className="settings-items">
45-
<div className="settings-items__item">
46-
<div className="settings-items__meta">
47-
<p className="settings-items__title">Service accounts</p>
48-
<p className="settings-items__description">Your loyal servants</p>
49-
<AddServiceAccountForm
50-
teamName={teamName}
51-
serviceAccountRevalidate={serviceAccountRevalidate}
52-
/>
53-
</div>
54-
<div className="settings-items__content">
55-
<ServiceAccountsTable
56-
serviceAccounts={serviceAccounts}
57-
serviceAccountRevalidate={serviceAccountRevalidate}
58-
teamName={teamName}
59-
/>
60-
</div>
61-
</div>
62-
</div>
45+
<Suspense fallback={<div>Loading...</div>}>
46+
<Await resolve={serviceAccounts}>
47+
{(resolvedServiceAccounts) => (
48+
<div className="settings-items">
49+
<div className="settings-items__item">
50+
<div className="settings-items__meta">
51+
<p className="settings-items__title">Service accounts</p>
52+
<p className="settings-items__description">
53+
Your loyal servants
54+
</p>
55+
<AddServiceAccountForm
56+
teamName={teamName}
57+
serviceAccountRevalidate={serviceAccountRevalidate}
58+
/>
59+
</div>
60+
<div className="settings-items__content">
61+
<ServiceAccountsTable
62+
serviceAccounts={resolvedServiceAccounts}
63+
serviceAccountRevalidate={serviceAccountRevalidate}
64+
teamName={teamName}
65+
/>
66+
</div>
67+
</div>
68+
</div>
69+
)}
70+
</Await>
71+
</Suspense>
6372
);
6473
}
6574

apps/cyberstorm-remix/app/settings/teams/team/tabs/Settings/Settings.tsx

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { faTrashCan } from "@fortawesome/free-solid-svg-icons";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3-
import { useReducer, useState } from "react";
4-
import { useLoaderData, useNavigate, useOutletContext } from "react-router";
3+
import { Suspense, useReducer, useState } from "react";
4+
import {
5+
Await,
6+
useLoaderData,
7+
useNavigate,
8+
useOutletContext,
9+
} from "react-router";
510

611
import "./Settings.css";
712
import {
@@ -28,20 +33,18 @@ import { useStrongForm } from "cyberstorm/utils/StrongForm/useStrongForm";
2833

2934
export const clientLoader = makeTeamSettingsTabLoader(
3035
// TODO: add end point for checking can leave/disband status.
31-
async (dapper, teamName) => ({})
36+
async (dapper, teamName) => ({ teamName })
3237
);
3338

3439
export default function Settings() {
3540
const { teamName } = useLoaderData<typeof clientLoader>();
3641
const outletContext = useOutletContext() as OutletContextShape;
37-
38-
if (!outletContext.currentUser || !outletContext.currentUser.username)
39-
return <NotLoggedIn />;
40-
4142
const toast = useToast();
42-
4343
const navigate = useNavigate();
4444

45+
const currentUser = outletContext.currentUser?.username;
46+
if (!currentUser) return <NotLoggedIn />;
47+
4548
async function moveToTeams() {
4649
toast.addToast({
4750
csVariant: "info",
@@ -52,57 +55,63 @@ export default function Settings() {
5255
}
5356

5457
return (
55-
<div className="settings-items">
56-
<div className="settings-items__item">
57-
<div className="settings-items__meta">
58-
<p className="settings-items__title">Leave team</p>
59-
<p className="settings-items__description">Leave your team</p>
60-
</div>
61-
<div className="settings-items__content">
62-
<NewAlert csVariant="danger">
63-
You cannot currently leave this team as you are it&apos;s last
64-
owner.
65-
</NewAlert>
66-
<p>
67-
If you are the owner of the team, you can only leave if the team has
68-
another owner assigned.
69-
</p>
70-
<LeaveTeamForm
71-
userName={outletContext.currentUser.username}
72-
teamName={teamName}
73-
toast={toast}
74-
config={outletContext.requestConfig}
75-
updateTrigger={moveToTeams}
76-
/>
77-
</div>
78-
</div>
79-
<div className="settings-items__separator" />
80-
<div className="settings-items__item">
81-
<div className="settings-items__meta">
82-
<p className="settings-items__title">Disband team</p>
83-
<p className="settings-items__description">
84-
Disband your team completely
85-
</p>
86-
</div>
87-
<div className="settings-items__content">
88-
<NewAlert csVariant="danger">
89-
You cannot currently disband this team as it has packages.
90-
</NewAlert>
91-
<p>You are about to disband the team {teamName}.</p>
92-
<p>
93-
Be aware you can currently only disband teams with no packages. If
94-
you need to archive a team with existing pages, contact Mythic#0001
95-
on the Thunderstore Discord.
96-
</p>
97-
<DisbandTeamForm
98-
teamName={teamName}
99-
updateTrigger={moveToTeams}
100-
config={outletContext.requestConfig}
101-
toast={toast}
102-
/>
103-
</div>
104-
</div>
105-
</div>
58+
<Suspense fallback={<div>Loading...</div>}>
59+
<Await resolve={teamName}>
60+
{(resolvedTeamName) => (
61+
<div className="settings-items">
62+
<div className="settings-items__item">
63+
<div className="settings-items__meta">
64+
<p className="settings-items__title">Leave team</p>
65+
<p className="settings-items__description">Leave your team</p>
66+
</div>
67+
<div className="settings-items__content">
68+
<NewAlert csVariant="danger">
69+
You cannot currently leave this team as you are it&apos;s last
70+
owner.
71+
</NewAlert>
72+
<p>
73+
If you are the owner of the team, you can only leave if the
74+
team has another owner assigned.
75+
</p>
76+
<LeaveTeamForm
77+
userName={currentUser}
78+
teamName={teamName}
79+
toast={toast}
80+
config={outletContext.requestConfig}
81+
updateTrigger={moveToTeams}
82+
/>
83+
</div>
84+
</div>
85+
<div className="settings-items__separator" />
86+
<div className="settings-items__item">
87+
<div className="settings-items__meta">
88+
<p className="settings-items__title">Disband team</p>
89+
<p className="settings-items__description">
90+
Disband your team completely
91+
</p>
92+
</div>
93+
<div className="settings-items__content">
94+
<NewAlert csVariant="danger">
95+
You cannot currently disband this team as it has packages.
96+
</NewAlert>
97+
<p>You are about to disband the team {teamName}.</p>
98+
<p>
99+
Be aware you can currently only disband teams with no
100+
packages. If you need to archive a team with existing pages,
101+
contact Mythic#0001 on the Thunderstore Discord.
102+
</p>
103+
<DisbandTeamForm
104+
teamName={teamName}
105+
updateTrigger={moveToTeams}
106+
config={outletContext.requestConfig}
107+
toast={toast}
108+
/>
109+
</div>
110+
</div>
111+
</div>
112+
)}
113+
</Await>
114+
</Suspense>
106115
);
107116
}
108117

0 commit comments

Comments
 (0)