-
Notifications
You must be signed in to change notification settings - Fork 280
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
feat(clerk-react): Support for fallback prop #4723
Conversation
🦋 Changeset detectedLatest commit: 952b536 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
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 |
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
…omponents exposed through clerk/nextjs
@@ -6,7 +6,7 @@ import { | |||
SignUp as BaseSignUp, | |||
UserProfile as BaseUserProfile, | |||
} from '@clerk/clerk-react'; | |||
import type { OrganizationProfileProps, SignInProps, SignUpProps, UserProfileProps } from '@clerk/types'; | |||
import type { ComponentProps } from 'react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥇
if (!elementToWatch && selector) { | ||
elementToWatch = root?.querySelector(selector); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's most definitely a smarter way to do this, it's not the most efficient to re-query on each observed mutation, but given that it's scoped to the time the component mounts to the time the clerk-js mount happens it shouldn't be running too many times...
if (!clerk.loaded && !options?.renderWhileLoading) { | ||
return null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added rendering of the fallback here initially, instead of allowing the component to render, but the fallback element was then injected twice.
* Used to orchestrate mounting of Clerk components in a host React application. | ||
* Components are rendered into a specific DOM node using mount/unmount methods provided by the Clerk class. | ||
*/ | ||
export class ClerkHostRenderer extends React.PureComponent< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is largely unchanged from the previous Portal
component. I renamed it to avoid confusion with React's portal primitive.
/> | ||
); | ||
}, 'SignIn'); | ||
export const SignIn = withClerk( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The diff for each component is mostly identical. I might take a pass at trying to DRY this up, but defaulting to avoiding too much abstraction.
/** | ||
* Used to detect when a Clerk component has been added to the DOM. | ||
*/ | ||
function waitForElementChildren(options: { selector?: string; root?: HTMLElement | null; timeout?: number }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is somewhat of a brutce-force way of detecting when one of our components mounts. Instead of trying to plumb through some sort of event or callback to internal clerk-js components, I'm instead using a MutationObserver
here to detect changes to the DOM. When the root element of the renderer has children (injected by clerk-js) we can assume the component has mounted.
const rendererRootProps = { | ||
...(shouldShowFallback && fallback && { style: { display: 'none' } }), | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClerkHostRenderer
always has to render, which is why we can't use Suspense, so to ensure the fallback and the component aren't mounted at the same time, we apply display: none
to the renderer while the fallback is loading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good to me so far!
const { customPages, customPagesPortals } = useUserProfileCustomPages(props.children); | ||
return ( | ||
<Portal | ||
<ClerkHostRenderer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing the rendering of fallback here for UserProfile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ 67e8e2e
); | ||
}, | ||
'UserProfile', | ||
{ component: 'UserProfile', renderWhileLoading: true }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In which scenario do we want this to be false
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existing behavior is for withClerk()
to not render its children until Clerk has loaded. We use it for most other components, so this lets us retain existing behavior without making a larger refactor.
I do think it's worth dropping the option in the future, we'll just need to update all usages 👍
const shouldShowFallback = mountingStatus === 'rendering' || !clerk.loaded; | ||
|
||
const rendererRootProps = { | ||
...(shouldShowFallback && fallback && { style: { display: 'none' } }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for accessibility reasons maybe we should go with <div hidden>
instead of a css rule ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@panteliselef whats the reasoning? Wouldn't hidden
be equivalent since the browsers default style for hidden
is display: none
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
styling wise yes, it's kind of nitpicking tbh, but afiak screen readers don't respect display:none;
but respect hidden
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
watcherRef.current = waitForElementChildren({ selector: `[data-clerk-component="${component}"]` }) | ||
.then(() => { | ||
setStatus('rendered'); | ||
}) | ||
.catch(() => { | ||
setStatus('error'); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure this works, but do you think we should something on cleanup and disconnect the observer or don't update the state ?
b10ec6c
to
87495b6
Compare
Description
Introduces a new
fallback
prop to Clerk's components. This lets users render a placeholder while Clerk is loading and the component mounts. Use to avoid layout shift and improve the loading UX.Usage:
fixes SDKI-795
Checklist
pnpm test
runs as expected.pnpm build
runs as expected.Type of change