Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dashboard): Form blocker fix #7710

Open
wants to merge 4 commits into
base: next
Choose a base branch
from
Open

fix(dashboard): Form blocker fix #7710

wants to merge 4 commits into from

Conversation

desiprisg
Copy link
Contributor

@desiprisg desiprisg commented Feb 12, 2025

What changed? Why was the change needed?

This PR fixes the blockers in our forms and also provides a unified way to protect forms.

  • Introduce a <FormRoot /> which renders a <form /> and adds a data-dirty attribute when dirty.
  • useFormProtection hooks receives a ref and detects when a dirty form exists as a descendant. Also protects from refreshes.
  • <FormProtection /> is returned from the hook renders our custom alert automatically.

Example usage:

  const {
    protectedOnOpenChange,
    ProtectionAlert,
    ref: protectionRef,
  } = useFormProtection({
    onOpenChange: setOpen,
  });

  const { ref: unmountRef } = useOnElementUnmount({
    callback: () => {
      navigate(-1);
    },
  });

  const combinedRef = useCombinedRefs(unmountRef, protectionRef);

  return (
    <>
      <Sheet open={open} onOpenChange={protectedOnOpenChange}>
        {/* Custom overlay since SheetOverlay does not work with modal={false} */}
        <div
          className={cn('fade-in animate-in fixed inset-0 z-50 bg-black/20 transition-opacity duration-300', {
            'pointer-events-none opacity-0': !open,
          })}
        />
        <SheetContent ref={combinedRef}>
          <CreateSubscriberForm onSuccess={() => navigate(-1)} />
        </SheetContent>
      </Sheet>

      <ProtectionAlert />
    </>
  );

Copy link

netlify bot commented Feb 12, 2025

Deploy Preview for dev-web-novu ready!

Name Link
🔨 Latest commit 1ad422b
🔍 Latest deploy log https://app.netlify.com/sites/dev-web-novu/deploys/67ae0d630e5cb70008fa1fd3
😎 Deploy Preview https://deploy-preview-7710.dashboard.novu-staging.co
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

netlify bot commented Feb 12, 2025

Deploy Preview for dashboard-v2-novu-staging failed. Why did it fail? →

Name Link
🔨 Latest commit 1ad422b
🔍 Latest deploy log https://app.netlify.com/sites/dashboard-v2-novu-staging/deploys/67ae0d633f098300089a67a6

Comment on lines 18 to 19
if (!ref.current) {
const documentObserver = new MutationObserver(() => {
if (ref.current) {
documentObserver.disconnect();
setupElementObserver(ref.current);
}
});

documentObserver.observe(document.body, {
childList: true,
subtree: true,
});

return () => {
documentObserver.disconnect();
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This part of the code could be removed if there is no element you didn't use the hook properly, and just do nothing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cleaned it up

Comment on lines 64 to 53
const protectedOnOpenChange = (open: boolean) => {
if (isDirty) {
setShowAlert(true);
} else {
onOpenChange?.(open);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

This function is a bit tied to the drawer/modal use; what we might want to do instead is to validate if we are allowed to do some action, for example, close the drawer or navigate to a different page.
So if we change this to const { triggerIsAllowed, ProtectionAlert } = useFormProtection(...), then it will be more flexible (names for you to decide).

const onProceed = () => setDrawerOpened(false);

const { triggerIsAllowed, ProtectionAlert } = useFormProtection({
    ref,
    onProceed,
  });

// similarly could be used with navigation or anything
const onDrawerOpenChange = (open: boolean) => {
   if (triggerIsAllowed()) {
      setDrawerOpen(open);
   }
} 

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is also an onProceed prop . You don't have to pass an onOpenChange and can choose to use the protectedOnChange only when you do.

Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need to have two props for different use cases.
Also the thing is that you always need to call protectedOnOpenChange even if you use onProceed and in case of navigation it's weird (naming).

apps/dashboard/src/hooks/use-form-protection.tsx Outdated Show resolved Hide resolved
};

const ProtectionAlert = useCallback(
() => (
Copy link
Contributor

Choose a reason for hiding this comment

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

btw this will create an anonymous react element in the tree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants