-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Display a generic fallback component when initial config load fails #8588
base: main
Are you sure you want to change the base?
Conversation
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.
PR Summary
This PR enhances error handling during initial configuration load by implementing a user-friendly error display when the backend server is unreachable.
- Introduced new
clientConfigApiStatusState
in/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts
to track both loading and error states - Reordered providers in
/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx
to ensure proper styling of error states - Modified
/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx
to display responsive error UI with mobile viewport support - Enhanced
/packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx
with initial fetch handling and location-based error boundary reset - Replaced deprecated
isClientConfigLoadedState
with more comprehensive status tracking across auth and config components
8 file(s) reviewed, 9 comment(s)
Edit PR Review Bot Settings | Greptile
<ApolloMetadataClientProvider> | ||
<ObjectMetadataItemsProvider> | ||
<AppThemeProvider> |
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.
logic: Moving ObjectMetadataItemsProvider before ClientConfigProvider could cause issues if metadata queries depend on client config values
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.
@khuddite I think the AI is right here, one example of a bug that was introduced is the always-loading state here:
From a system architecture perspective I think it makes more sense to have the clientConfig stay at the higher-level. It's definitely a tougher problem than it looked initially!
I think what would make the most sense is to introduce a BaseThemeProvider
at a higher level that would be similar to AppThemeProvider but with a simplified logic just based on system preference. This might be helpful if we start having more logged-out pages...
We can then rename AppThemeProvider
to UserThemeProvider
and focus its role on updating the context only based on the WorkspaceMember.
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.
That would be a great solution to the problem. Thanks!
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 addition to that, I will see if we can move ErrorBoundary
up to the top level, so the initial config load error gets caught by ErrorBoundary
(not manually like we do now). If so, we won't even need to introduce any additional states like isErrored
or errorMessage
. The position of ErrorBoundary
wrapper in the current setup doesn't quite make sense to me.
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 issue to move ErrorBoundary is that the Sentry config comes from ClientConfig... Maybe one day we should push it during the build process in env-config.js instead but today it won't work I think 🤔
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.
That's a good callout, thanks! I could've wasted a lot of time just to figure that out 😁
return ( | ||
<StyledContainer> | ||
<GenericErrorFallback | ||
error={new Error('Failed to fetch')} |
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.
logic: Generic 'Failed to fetch' error may not be descriptive enough of the actual error that occurred. Consider passing the actual error from the API call.
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.
@khuddite Agree with the AI comment, passing the error message from the API call would make sense (add errorMessage in your state?)
<StyledContainer> | ||
<GenericErrorFallback | ||
error={new Error('Failed to fetch')} | ||
resetErrorBoundary={() => {}} |
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.
logic: Empty resetErrorBoundary callback means users can't retry when the error occurs. Consider implementing a retry mechanism.
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.
Didn't test that but yes we should be able to reload from an error page
See also #5027 @ehconitin will work on this now so it might be fixed as part of his PR
|
||
return isClientConfigLoaded ? <>{children}</> : <></>; | ||
return isLoaded ? <>{children}</> : 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.
style: Consider showing a loading state instead of null while isLoaded is 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.
would return isLoaded ? children : null;
work then? If you decide not to wrap in component makes sense to do it on both side
setClientConfigApiStatus((currentStatus) => ({ | ||
...currentStatus, | ||
isLoaded: 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.
logic: Setting isLoaded:true before checking for errors could cause race conditions. Move this after error checks.
if (!isDefined(data?.clientConfig)) { | ||
return; | ||
} |
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.
logic: Should set isErrored:true here since missing clientConfig is an error state
setAuthProviders({ | ||
google: data?.clientConfig.authProviders.google, | ||
microsoft: data?.clientConfig.authProviders.microsoft, | ||
password: data?.clientConfig.authProviders.password, | ||
magicLink: false, | ||
sso: data?.clientConfig.authProviders.sso, | ||
}); |
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.
style: Optional chaining is inconsistent - using ?. for clientConfig but not for nested authProviders properties
<PageBody> | ||
<AnimatedPlaceholderEmptyContainer> | ||
<AnimatedPlaceholder type="errorIndex" /> | ||
<AnimatedPlaceholderEmptyTextContainer> | ||
<AnimatedPlaceholderEmptyTitle> | ||
Server’s on a coffee break | ||
{title} | ||
</AnimatedPlaceholderEmptyTitle> | ||
<AnimatedPlaceholderEmptySubTitle> | ||
{error.message} |
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.
logic: error.message may be undefined - consider adding a fallback message
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.
Almost there!! Thanks a lot!
|
||
if (isLoaded && isErrored) { | ||
return ( | ||
<StyledContainer> |
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.
Could you please extract this styled container into its own component next to GenericErrorFallback? Eg. ClientConfigError
?
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.
Sure, that's a good idea!
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.
Thanks! Just to be clear I didn't mean just the styled container but do a big wraper component. I think my comment wasn't clear
|
||
export const GenericErrorFallback = ({ | ||
error, | ||
resetErrorBoundary, | ||
title = 'Something went wrong', | ||
isInitialFetch = 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.
I think it would be a cleaner component API (and more reusable) to just name this as showPageHeader
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 flag is also used to determine the visibility of refetch
button. I am not sure if showPageHeader
is still a good name in that case.
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.
Let's assume that it will be hard refresh in the future! Even if it's broken for a few days it should come soon
<ApolloMetadataClientProvider> | ||
<ObjectMetadataItemsProvider> | ||
<AppThemeProvider> |
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.
@khuddite I think the AI is right here, one example of a bug that was introduced is the always-loading state here:
From a system architecture perspective I think it makes more sense to have the clientConfig stay at the higher-level. It's definitely a tougher problem than it looked initially!
I think what would make the most sense is to introduce a BaseThemeProvider
at a higher level that would be similar to AppThemeProvider but with a simplified logic just based on system preference. This might be helpful if we start having more logged-out pages...
We can then rename AppThemeProvider
to UserThemeProvider
and focus its role on updating the context only based on the WorkspaceMember.
…splitting and address PR comments
@FelixMalfait Hope these changes look better now, thanks for your comments! |
I intentionally threw an error and this is how it looks like for normal exceptions. This recording is for #5027 CleanShot.2024-11-21.at.16.02.55.mp4 |
Fixes: #8487 #5027
Summary
The purpose of these changes is to elevate the dev/user experience when the initial config load call fails for whatever reason by displaying a fallback component.
Solution
I ended up making more changes than I initially planned. I had to update the order of the contexts a bit because
GenericErrorFallback
is dependent onAppThemeProvider
for styling andAppThemeProvider
is dependent onObjectMetadataItemsProvider
foruseObjectMetadataItem
hook (AppThemeProvider
->useColorScheme
->useUpdateOneRecord
->useObjectMetadataItem
). I had to create a wrapper component forAppThemeProvider
and stylize it in a way that it looks responsive on both mobile and desktop devices. Finally, I had to introduce theisErrored
flag to differentiate the loading and error states.There are some improvements we can make later -
Recording
CleanShot.2024-11-19.at.10.24.53.mp4
CleanShot.2024-11-19.at.10.25.51.mp4