Skip to content

Commit ecc2aeb

Browse files
committed
fix(webapp): split publish-draft dialog form to satisfy set-state-in-effect lint
The publish-draft dialog closed itself by calling a state setter inside an effect, which the react/set-state-in-effect rule flags. Move the form into a child component that closes via an onClose prop instead, matching the existing edit form. No behavior change.
1 parent 1e131f2 commit ecc2aeb

1 file changed

Lines changed: 62 additions & 49 deletions

File tree

apps/webapp/app/routes/admin.notifications.tsx

Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -787,11 +787,6 @@ function PublishNowButton({ notificationId }: { notificationId: string }) {
787787

788788
function PublishDraftButton({ notificationId }: { notificationId: string }) {
789789
const [open, setOpen] = useState(false);
790-
const fetcher = useFetcher<{ success?: boolean; error?: string }>();
791-
792-
useEffect(() => {
793-
if (fetcher.data?.success) setOpen(false);
794-
}, [fetcher.data]);
795790

796791
return (
797792
<>
@@ -803,56 +798,74 @@ function PublishDraftButton({ notificationId }: { notificationId: string }) {
803798
<DialogHeader>
804799
<DialogTitle>Publish notification</DialogTitle>
805800
</DialogHeader>
806-
<fetcher.Form method="post" className="space-y-3">
807-
<input type="hidden" name="_action" value="publish-draft" />
808-
<input type="hidden" name="notificationId" value={notificationId} />
809-
<Paragraph variant="small" className="text-text-dimmed">
810-
Set the schedule for this notification. It becomes visible to users at the start time.
811-
</Paragraph>
812-
<div className="grid grid-cols-2 gap-3">
813-
<div>
814-
<Label variant="small">
815-
Starts at (UTC) <span className="text-red-400">*</span>
816-
</Label>
817-
<input
818-
name="startsAt"
819-
type="datetime-local"
820-
defaultValue={toDatetimeLocalUTC(new Date())}
821-
className="mt-1 block h-8 w-full rounded border border-background-bright bg-background-hover px-2 text-sm text-text-bright transition hover:border-border-bright hover:bg-secondary"
822-
required
823-
/>
824-
</div>
825-
<div>
826-
<Label variant="small">
827-
Ends at (UTC) <span className="text-red-400">*</span>
828-
</Label>
829-
<input
830-
name="endsAt"
831-
type="datetime-local"
832-
defaultValue={defaultEndsAt()}
833-
className="mt-1 block h-8 w-full rounded border border-background-bright bg-background-hover px-2 text-sm text-text-bright transition hover:border-border-bright hover:bg-secondary"
834-
required
835-
/>
836-
</div>
837-
</div>
838-
<DialogFooter className="items-center">
839-
{fetcher.data?.error && (
840-
<span className="text-xs text-red-400">{fetcher.data.error}</span>
841-
)}
842-
<Button type="button" variant="tertiary/medium" onClick={() => setOpen(false)}>
843-
Cancel
844-
</Button>
845-
<Button type="submit" variant="primary/medium" disabled={fetcher.state !== "idle"}>
846-
{fetcher.state !== "idle" ? "Publishing..." : "Publish"}
847-
</Button>
848-
</DialogFooter>
849-
</fetcher.Form>
801+
<PublishDraftForm notificationId={notificationId} onClose={() => setOpen(false)} />
850802
</DialogContent>
851803
</Dialog>
852804
</>
853805
);
854806
}
855807

808+
// Split out so the "close on success" effect calls the `onClose` prop rather than a
809+
// local state setter — the latter trips react/set-state-in-effect.
810+
function PublishDraftForm({
811+
notificationId,
812+
onClose,
813+
}: {
814+
notificationId: string;
815+
onClose: () => void;
816+
}) {
817+
const fetcher = useFetcher<{ success?: boolean; error?: string }>();
818+
819+
useEffect(() => {
820+
if (fetcher.data?.success) onClose();
821+
}, [fetcher.data, onClose]);
822+
823+
return (
824+
<fetcher.Form method="post" className="space-y-3">
825+
<input type="hidden" name="_action" value="publish-draft" />
826+
<input type="hidden" name="notificationId" value={notificationId} />
827+
<Paragraph variant="small" className="text-text-dimmed">
828+
Set the schedule for this notification. It becomes visible to users at the start time.
829+
</Paragraph>
830+
<div className="grid grid-cols-2 gap-3">
831+
<div>
832+
<Label variant="small">
833+
Starts at (UTC) <span className="text-red-400">*</span>
834+
</Label>
835+
<input
836+
name="startsAt"
837+
type="datetime-local"
838+
defaultValue={toDatetimeLocalUTC(new Date())}
839+
className="mt-1 block h-8 w-full rounded border border-background-bright bg-background-hover px-2 text-sm text-text-bright transition hover:border-border-bright hover:bg-secondary"
840+
required
841+
/>
842+
</div>
843+
<div>
844+
<Label variant="small">
845+
Ends at (UTC) <span className="text-red-400">*</span>
846+
</Label>
847+
<input
848+
name="endsAt"
849+
type="datetime-local"
850+
defaultValue={defaultEndsAt()}
851+
className="mt-1 block h-8 w-full rounded border border-background-bright bg-background-hover px-2 text-sm text-text-bright transition hover:border-border-bright hover:bg-secondary"
852+
required
853+
/>
854+
</div>
855+
</div>
856+
<DialogFooter className="items-center">
857+
{fetcher.data?.error && <span className="text-xs text-red-400">{fetcher.data.error}</span>}
858+
<Button type="button" variant="tertiary/medium" onClick={onClose}>
859+
Cancel
860+
</Button>
861+
<Button type="submit" variant="primary/medium" disabled={fetcher.state !== "idle"}>
862+
{fetcher.state !== "idle" ? "Publishing..." : "Publish"}
863+
</Button>
864+
</DialogFooter>
865+
</fetcher.Form>
866+
);
867+
}
868+
856869
function DeleteConfirmationButton({ notificationId }: { notificationId: string }) {
857870
const [open, setOpen] = useState(false);
858871
const fetcher = useFetcher();

0 commit comments

Comments
 (0)