Skip to content

Fix <Edit>, <ReferenceField> and <List> to better support offline mode#11161

Open
fzaninotto wants to merge 20 commits intomasterfrom
offline-example
Open

Fix <Edit>, <ReferenceField> and <List> to better support offline mode#11161
fzaninotto wants to merge 20 commits intomasterfrom
offline-example

Conversation

@fzaninotto
Copy link
Member

@fzaninotto fzaninotto commented Feb 12, 2026

Problem

Offline apps behave in a strange way, sometimes showing error pages even though they should not.

The offline mode of TanStack Query works fine in a standalone example, so the problems come from react-admin.

We lack a way to properly test offline mode, particularly in the context of a PWA.

Additional specific issues found:

  • <Edit> ignores the redirectOnError prop, making it hard to disable the redirection to the list in case of error (even if there's data in the cache)
  • <ReferenceField> renders an error message even if the reference record is available in the cache
  • <ListView> hides the pagination as soon as a fetch error occurs, preventing the user to go back to a page for which cached data are present

Solution

Build a demo (with Vite PWA plugin), track down the issues and fix them.

When offline, all calls to useQuery return an error... but they also return data.

In a nutshell, we must not only check for errors before deciding to show/return things but also that no data is available for offline support to work correctly

  • Fix ReferenceField
  • Fix ListView
  • Fix Edit
  • Test on a larger app (e.g. Atomic CRM)

If we have a NetworkError and valid data, it means we're offline and that TanStack query returned stale data.

How To Test

  • make build-offline
  • make preview-offline

(the offline mode doesn't work in dev mode, that's a limitation of vite-pwa)

Use the DevTools to simulate Offline mode when needed.

The PWA should be able to fetch data from the cache, even when reloading the whole app while offline (thanks to the service worker and the TanStack query Persister).

Known issue: TanStack query discards the cached data if you reload the app 2 times. See related issue.

The PWA should allow to perform mutations while offline, and replay the mutations when back online.

Note: Since this demo is hooked up to JSONPlaceholder API, the changes are not persisted for real. But the mutation should be visible in the Network tab and not trigger errors.

Additional Checks

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

@fzaninotto fzaninotto added the WIP Work In Progress label Feb 12, 2026
@slax57 slax57 changed the title Fix offline Fix <Edit>, <ReferenceField> and <List> to better support offline mode Feb 17, 2026
@slax57 slax57 added RFR Ready For Review WIP Work In Progress and removed WIP Work In Progress RFR Ready For Review labels Feb 17, 2026
const getRecordRepresentation = useGetRecordRepresentation(reference);

if (error) {
if (error && referenceRecord == null) {
Copy link
Collaborator

@WiXSL WiXSL Feb 18, 2026

Choose a reason for hiding this comment

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

When the app is online and a cached referenceRecord exists, it can hide real API errors

// Potencial Fix
if (error && (referenceRecord == null || !referenceFieldContext.isPaused)) {

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good point but isPaused is not guaranteed to reflect the actual connectivity status all the time (it can take some time for the device to actually mark the connectivity status as offline in real-life conditions).

Besides, it can be argued that displaying the reference record is more useful to the user than displaying only the error, even if the data is not fresh.

Alternatively, we could also introduce a prop to let the user decide ultimately (just like we have a redirectOnError prop on <Edit> that can be set to false)...

@fzaninotto wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

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

We're using the stale-while-revalidate approach heavily in react-admin, the idea being that the SPA architecture allows apps to be more useful to the user. Besides, there is still the possibility to both show the stale data and an error notification by using queryOptions.onError . So I think we can go with if (error && referenceRecord == null) { here.

@@ -0,0 +1,26 @@
# ra-offline

Copy link
Member Author

Choose a reason for hiding this comment

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

deserves a one line explanation of the demo puropse and usage

"@tanstack/query-async-storage-persister": "^5.90.22",
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-query-persist-client": "^5.90.22",
"ra-core": "^5.14.1",
Copy link
Member Author

Choose a reason for hiding this comment

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

not necessary (ra-ui-mui neither)

export const Layout = ({ children }: { children: ReactNode }) => (
<RALayout>
{children}
<CheckForApplicationUpdate />
Copy link
Member Author

Choose a reason for hiding this comment

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

unnecessary. But we'd benefit from the react query dev tools

@@ -0,0 +1,14 @@
import { DataTable, List, ReferenceField } from "react-admin";
Copy link
Member Author

Choose a reason for hiding this comment

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

no difference with a ListGuesser, we should remove it

<title>My Awesome App</title>
<meta name="description" content="My Awesome App description" />
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" sizes="180x180" />
Copy link
Member Author

Choose a reason for hiding this comment

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

we can simplify by removing the favicons and loading css

"react": "^19.0.0",
"react-admin": "^5.14.1",
"react-dom": "^19.0.0",
"react-router": "^7.1.3",
Copy link
Member Author

Choose a reason for hiding this comment

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

same, not sure it's required

"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0",
"globals": "^16.0.0",
"https-localhost": "^4.7.1",
Copy link
Member Author

Choose a reason for hiding this comment

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

how is this used?

languageName: node
linkType: hard

"@rollup/pluginutils@npm:^3.1.0":
Copy link
Member Author

Choose a reason for hiding this comment

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

some duplicates here, can we avoid them?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not they are not duplicates, the major version is different (5 vs 3).

We can't avoid the two versions as one is needed by astro and the other one by the vite pwa plugin.

I already ran yarn dedupe btw so they should not remain any duplicates.

"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"serve": "REINSTALL=true PORT=4433 serve dist",
Copy link
Collaborator

@WiXSL WiXSL Feb 19, 2026

Choose a reason for hiding this comment

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

make serve-offline fails
serve Should be install in devDependencies

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually I kept this command since it was added by @djhi and figured it was probably useful to force reinstalling the PWA on a real mobile device or something similar. But in the end, I did not use this command, force-reinstalling the PWA is easy enough to do with the desktop browser devtools (and on mobile device you can always remove and reinstall the app), so since it doesn't work I guess I'll simply remove it.

Copy link
Collaborator

@WiXSL WiXSL left a comment

Choose a reason for hiding this comment

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

Other than the requested changes, I tested both in development and production (make build-offline + make preview-offline): offline behavior works as expected, including cached navigation/edit flows and mutation replay when reconnecting. Looks good to me.

Good Job 💪

<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" sizes="180x180" />
<link rel="mask-icon" href="/maskable-icon-512x512.png" color="#FFFFFF" />
<meta name="theme-color" content="#ffffff" />
<title>ra-offline</title>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Double title

Copy link
Contributor

Choose a reason for hiding this comment

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

nice catch!

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

Comments