Skip to content

Add support for offline/local first applications #10545

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

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

Conversation

djhi
Copy link
Collaborator

@djhi djhi commented Feb 24, 2025

Problem

React-admin currently don't handle well the loss of network connectivity: in any demo, if you go to a list page, then simulate being offline using the devtool and try to go to the second page of the same list, it will still display the first page data while the pagination will indeed indicate you are on page 2.

We handled it a bit better by at least showing users a notification when they try to load new data while offline but we can do better and allow for resumable mutations.

Solution

  • Needs Add support for mutationMode in useCreate #10530
  • Make sure the ListContext include the isPaused and isPlaceholderData props from react-query and update components/hooks accordingly
  • Show the empty component when those two props are true (ideally we should have a dedicated component for that or handle the case in the default empty but this is achievable in userland)
  • Add a mutationKey to all mutations so that we can provide default functions for them at the queryClient level (required by react-query: https://tanstack.com/query/latest/docs/framework/react/guides/mutations#persisting-offline-mutations)
  • Make the simple example work offline (no pwa yet though)
  • Add an offline prop to reference fields components
  • Add an offline prop to list components
  • Add an offline prop to show and edit views
  • Add an offline prop to refenrenc inputs components
  • Update documentation

Screenshots

Edit view
image

Show view
image

DataTable (same for all list child components
image

How To Test

  • Open the simple example
  • Use the browser devtool to go offline
  • Click the posts create button and fill it
  • Go back to the posts list after and notice the new item is there
  • Open the devtool and note there is no create call to the dataProvider
  • Use the browser devtool to go online
  • Open the devtool and note there is a create call to the dataProvider

Then:

  • Follow the same steps but select the optimistic option in the form toolbar first.
  • Follow the same steps but click the undoable option in the form toolbar first.
  • Follow the same steps but edit a record instead of creating it.
  • Follow the same steps for both edit and create but enter f00bar as the post title to trigger an error once the network is online.

In stories for list components, reference components and detail views, simply switch to offline and hit the storybook refresh button

Additional Checks

  • The PR targets master for a bugfix, or next for a feature
  • The PR includes unit tests (if not possible, describe why)
  • The PR includes one or several stories (if not possible, describe why)
  • The documentation is up to date

Also, please make sure to read the contributing guidelines.

EditPostPage.setInputValue(
'input',
'title',
'Lorem Ipsum again{enter}'
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because react-query now persist queries and mutations for offline mode, the previous test now leaks into the second (e.g. this post has its title changed to Lorem Ipsum). I tried to configure testIsolation in Cypress but our version is probably too old

@djhi djhi added RFR Ready For Review and removed WIP Work In Progress labels Apr 30, 2025
@djhi
Copy link
Collaborator Author

djhi commented Apr 30, 2025

I can't see a way to test this except e2e. Not sure if we can simulate network loss with cypress though

@slax57
Copy link
Contributor

slax57 commented Apr 30, 2025

@djhi since the simple demo has the react-query DevTools enabled, you can use them to simulate network loss I believe (clicking on the 'Wifi' icon does the job). A bit ugly but it should work!

Copy link
Contributor

@erwanMarmelab erwanMarmelab left a comment

Choose a reason for hiding this comment

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

Praise: very understandable doc !!

@djhi djhi added WIP Work In Progress and removed RFR Ready For Review labels May 9, 2025
Comment on lines 91 to 92
actions?: ReactNode | false;
aside?: ReactNode;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No BC here ,ReactNode include ReactElement and false

@djhi djhi added RFR Ready For Review and removed WIP Work In Progress labels May 15, 2025
@fzaninotto
Copy link
Member

When going offline, I don't understand why I can't go to the detail view of a post when I've already seen it in the list view. The getList populates the cache, right?

@fzaninotto
Copy link
Member

The 'how to test' instructions must be updated, as the tester must select the optimistic mutation mode before submitting the form.

@fzaninotto
Copy link
Member

When doing an optimistic offline mutation, the loader in the app bar keeps spinning forever even tough the optimistic update is already applied. Is it a good UI? Instead, I think we should have a way to mention the pending updates, e.g. using a badge on the loader icon.

Copy link
Member

@fzaninotto fzaninotto left a comment

Choose a reason for hiding this comment

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

We're getting closer!

</AppBar>
);
const MyAppBar = () => {
const isOnline = useIsOnline();
Copy link
Member

Choose a reason for hiding this comment

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

I'd name the hook useIsOffline instead, since you only ever check that it's false.

)
```
{% endraw %}

Copy link
Member

Choose a reason for hiding this comment

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

You're missing a sentence to explain that the setup is done here - the rest is only for developers with custom methods.

@@ -95,6 +117,10 @@ const Root = styled('div', {
[`& .${EditClasses.noActions}`]: {
marginTop: '1em',
},
[`& .${EditClasses.offline}`]: {
Copy link
Member

Choose a reason for hiding this comment

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

Code smell: The Offline component should work as is, without CSS customization.

Comment on lines -125 to +142
<div>
<Stack direction="row" alignItems="center" gap={1}>
<ErrorIcon role="presentation" color="error" fontSize="small" />
<span style={visuallyHidden}>
{typeof error === 'string' ? error : error?.message}
</span>
</div>
<Typography
component="span"
variant="body2"
sx={{ color: 'error.main' }}
>
<Translate i18nKey="ra.notification.http_error">
Server communication error
</Translate>
</Typography>
</Stack>
Copy link
Member

Choose a reason for hiding this comment

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

You've changed the content and design here. Can you elaborate on why you did that?

Copy link
Collaborator Author

@djhi djhi May 19, 2025

Choose a reason for hiding this comment

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

We only had an icon for error before with no message for users not using assistive tools. We discussed this together

}

if (
!record ||
Copy link
Member

Choose a reason for hiding this comment

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

this is already in the previous condition

{...rest}
offline={
offline ?? (
<Labeled {...rest}>
Copy link
Member

Choose a reason for hiding this comment

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

It feels weird to repeat rest several times, plus some aren't compatible with Labeled. I wouldn't apply them here, and pass the source and label props explicitly instead.

@@ -13,6 +13,7 @@ export * from './ListGuesser';
export * from './ListNoResults';
export * from './ListToolbar';
export * from './ListView';
export * from '../Offline';
Copy link
Member

Choose a reason for hiding this comment

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

reexported from the wrong directory (the .. is weird here)

Co-authored-by: Francois Zaninotto <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RFR Ready For Review
Development

Successfully merging this pull request may close these issues.

4 participants