- {offline}
+
+ {offline}
);
@@ -111,10 +105,6 @@ const Root = styled('div', {
[`& .${ShowClasses.noActions}`]: {
marginTop: '1em',
},
- [`& .${ShowClasses.offline}`]: {
- flexDirection: 'column',
- alignItems: 'unset',
- },
[`& .${ShowClasses.card}`]: {
flex: '1 1 auto',
},
From 84207e0989c447739ddeebb7577a87a8be1e1358 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Tue, 20 May 2025 16:11:52 +0200
Subject: [PATCH 37/58] Rename isOnline hook to useIsOffline
---
examples/simple/src/Layout.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/examples/simple/src/Layout.tsx b/examples/simple/src/Layout.tsx
index 491cae62269..beb9249aa00 100644
--- a/examples/simple/src/Layout.tsx
+++ b/examples/simple/src/Layout.tsx
@@ -13,12 +13,12 @@ import OfflineIcon from '@mui/icons-material/SignalWifiConnectedNoInternet4';
import '../assets/app.css';
const MyAppBar = () => {
- const isOnline = useIsOnline();
+ const isOffline = useIsOffine();
return (
- {!isOnline ? (
+ {isOffline ? (
(
>
);
-const useIsOnline = () => {
+const useIsOffine = () => {
const [isOnline, setIsOnline] = React.useState(onlineManager.isOnline());
React.useEffect(() => {
@@ -58,7 +58,7 @@ const useIsOnline = () => {
return onlineManager.subscribe(handleChange);
}, []);
- return isOnline;
+ return !isOnline;
};
/**
From 2e2e10cd058d130c7ec2ac2555956df3b7eecbc9 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 5 Jun 2025 17:51:30 +0200
Subject: [PATCH 38/58] Improve Offline component design for reference fields
and inputs
---
docs/DataProviders.md | 4 +++
examples/simple/src/comments/CommentList.tsx | 4 ++-
examples/simple/src/posts/PostEdit.tsx | 19 ++++++++++++
examples/simple/src/posts/PostShow.tsx | 30 ++++++++++++++++++-
examples/simple/src/tags/TagList.tsx | 23 ++++++++++----
packages/ra-ui-materialui/src/Offline.tsx | 29 +++++++++++++++---
.../src/field/ReferenceField.tsx | 2 +-
.../src/field/ReferenceManyCount.tsx | 2 +-
.../src/field/ReferenceOneField.tsx | 2 +-
.../src/input/ReferenceArrayInput.tsx | 8 +++--
.../src/list/SingleFieldList.tsx | 2 +-
11 files changed, 108 insertions(+), 17 deletions(-)
diff --git a/docs/DataProviders.md b/docs/DataProviders.md
index e05009063ec..4bf666a7425 100644
--- a/docs/DataProviders.md
+++ b/docs/DataProviders.md
@@ -945,6 +945,10 @@ export const App = () => (
```
{% endraw %}
+This is enough to make all the standard react-admin features support offline scenarios.
+
+## Adding Offline Support To Custom Mutations
+
If you have [custom mutations](./Actions.md#calling-custom-methods) on your dataProvider, you can enable offline support for them too. For instance, if your `dataProvider` exposes a `banUser()` method:
```ts
diff --git a/examples/simple/src/comments/CommentList.tsx b/examples/simple/src/comments/CommentList.tsx
index cdc6bb02d9d..23b4f365f20 100644
--- a/examples/simple/src/comments/CommentList.tsx
+++ b/examples/simple/src/comments/CommentList.tsx
@@ -30,6 +30,7 @@ import {
useListContext,
useTranslate,
Exporter,
+ Offline,
} from 'react-admin';
const commentFilters = [
@@ -63,9 +64,10 @@ const exporter: Exporter = (records, fetchRelatedRecords) =>
});
const CommentGrid = () => {
- const { data } = useListContext();
+ const { data, isPaused, isPlaceholderData } = useListContext();
const translate = useTranslate();
+ if (isPaused && (data == null || isPlaceholderData)) return ;
if (!data) return null;
return (
diff --git a/examples/simple/src/posts/PostEdit.tsx b/examples/simple/src/posts/PostEdit.tsx
index e45a2c2b736..a88acfc5e3a 100644
--- a/examples/simple/src/posts/PostEdit.tsx
+++ b/examples/simple/src/posts/PostEdit.tsx
@@ -31,6 +31,7 @@ import {
useCreateSuggestionContext,
EditActionsProps,
CanAccess,
+ Translate,
} from 'react-admin';
import {
Box,
@@ -40,7 +41,9 @@ import {
DialogActions,
DialogContent,
TextField as MuiTextField,
+ Tooltip,
} from '@mui/material';
+import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined';
import PostTitle from './PostTitle';
import TagReferenceInput from './TagReferenceInput';
@@ -229,6 +232,22 @@ const PostEdit = () => (
reference="comments"
target="post_id"
sx={{ lineHeight: 'inherit' }}
+ offline={
+
+ }
+ >
+ theme.spacing(0.5),
+ }}
+ />
+
+ }
/>
}
>
diff --git a/examples/simple/src/posts/PostShow.tsx b/examples/simple/src/posts/PostShow.tsx
index e3e96c84656..0921a591239 100644
--- a/examples/simple/src/posts/PostShow.tsx
+++ b/examples/simple/src/posts/PostShow.tsx
@@ -23,7 +23,10 @@ import {
useShowController,
useLocaleState,
useRecordContext,
+ Translate,
} from 'react-admin';
+import { Tooltip } from '@mui/material';
+import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined';
import PostTitle from './PostTitle';
const CreateRelatedComment = () => {
@@ -112,11 +115,36 @@ const PostShow = () => {
span': {
+ display: 'flex',
+ alignItems: 'center',
+ },
+ }}
count={
+ }
+ >
+
+ theme.spacing(0.5),
+ }}
+ />
+
+ }
/>
}
>
diff --git a/examples/simple/src/tags/TagList.tsx b/examples/simple/src/tags/TagList.tsx
index bf3dd443ee5..ff2329705f4 100644
--- a/examples/simple/src/tags/TagList.tsx
+++ b/examples/simple/src/tags/TagList.tsx
@@ -6,6 +6,7 @@ import {
useListContext,
EditButton,
Title,
+ Offline,
} from 'react-admin';
import {
Box,
@@ -25,15 +26,27 @@ const TagList = () => (
-
-
-
-
-
+
);
+const TagListView = () => {
+ const { data, isPaused } = useListContext();
+
+ if (isPaused && data == null) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+ );
+};
+
const Tree = () => {
const { data, defaultTitle } = useListContext();
const [openChildren, setOpenChildren] = useState([]);
diff --git a/packages/ra-ui-materialui/src/Offline.tsx b/packages/ra-ui-materialui/src/Offline.tsx
index 953fad29111..d80766bed7a 100644
--- a/packages/ra-ui-materialui/src/Offline.tsx
+++ b/packages/ra-ui-materialui/src/Offline.tsx
@@ -7,9 +7,10 @@ import {
Typography,
} from '@mui/material';
import { useGetResourceLabel, useResourceContext, useTranslate } from 'ra-core';
+import clsx from 'clsx';
export const Offline = (props: Offline) => {
- const { message: messageProp } = props;
+ const { icon, message: messageProp, variant = 'standard', ...rest } = props;
const translate = useTranslate();
const resource = useResourceContext(props);
const getResourceLabel = useGetResourceLabel();
@@ -31,23 +32,43 @@ export const Offline = (props: Offline) => {
}
);
return (
-
+
{message}
);
};
-export interface Offline extends AlertProps {
+export interface Offline extends Omit {
resource?: string;
message?: string;
+ variant?: AlertProps['variant'] | 'inline';
}
const PREFIX = 'RaOffline';
+export const OfflineClasses = {
+ root: `${PREFIX}-root`,
+ inline: `${PREFIX}-inline`,
+};
const Root = styled(Alert, {
name: PREFIX,
overridesResolver: (props, styles) => styles.root,
-})(() => ({}));
+})(() => ({
+ [`&.${OfflineClasses.inline}`]: {
+ border: 'none',
+ display: 'inline-flex',
+ padding: 0,
+ margin: 0,
+ },
+}));
declare module '@mui/material/styles' {
interface ComponentNameToClassKey {
diff --git a/packages/ra-ui-materialui/src/field/ReferenceField.tsx b/packages/ra-ui-materialui/src/field/ReferenceField.tsx
index 1a12b899e29..23d3c4fe9aa 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceField.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceField.tsx
@@ -105,7 +105,7 @@ export interface ReferenceFieldProps<
// useful to prevent click bubbling in a datagrid with rowClick
const stopPropagation = e => e.stopPropagation();
-const defaultOffline = ;
+const defaultOffline = ;
export const ReferenceFieldView = <
RecordType extends Record = Record,
diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx
index a31da69b352..25bb45d516b 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx
@@ -21,7 +21,7 @@ import { sanitizeFieldRestProps } from './sanitizeFieldRestProps';
import { Link } from '../Link';
import { Offline } from '../Offline';
-const defaultOffline = ;
+const defaultOffline = ;
/**
* Fetch and render the number of records related to the current one
diff --git a/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx b/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx
index 5079bb547f0..929082914df 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx
@@ -19,7 +19,7 @@ import { FieldProps } from './types';
import { ReferenceFieldView } from './ReferenceField';
import { Offline } from '../Offline';
-const defaultOffline = ;
+const defaultOffline = ;
/**
* Render the related record in a one-to-one relationship
diff --git a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx
index 5d69ad47451..4049e3deca5 100644
--- a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx
+++ b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx
@@ -101,8 +101,12 @@ export const ReferenceArrayInput = (props: ReferenceArrayInputProps) => {
return isPaused && allChoices == null ? (
offline ?? (
-
-
+
+
)
) : (
diff --git a/packages/ra-ui-materialui/src/list/SingleFieldList.tsx b/packages/ra-ui-materialui/src/list/SingleFieldList.tsx
index a82d2acb198..c85b3a865ef 100644
--- a/packages/ra-ui-materialui/src/list/SingleFieldList.tsx
+++ b/packages/ra-ui-materialui/src/list/SingleFieldList.tsx
@@ -209,4 +209,4 @@ declare module '@mui/material/styles' {
}
}
-const DefaultOffline = ;
+const DefaultOffline = ;
From 8452c4f966615d37991647d332f65ec4bd23615c Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Fri, 6 Jun 2025 16:28:11 +0200
Subject: [PATCH 39/58] Make sure users know about pending operations
---
examples/simple/src/Layout.tsx | 16 +----
packages/ra-core/src/core/index.ts | 1 +
packages/ra-core/src/core/useIsOffine.ts | 21 ++++++
packages/ra-language-english/src/index.ts | 2 +
packages/ra-language-french/src/index.ts | 2 +
.../src/layout/LoadingIndicator.tsx | 67 +++++++++++++++----
6 files changed, 82 insertions(+), 27 deletions(-)
create mode 100644 packages/ra-core/src/core/useIsOffine.ts
diff --git a/examples/simple/src/Layout.tsx b/examples/simple/src/Layout.tsx
index beb9249aa00..498aa61d1aa 100644
--- a/examples/simple/src/Layout.tsx
+++ b/examples/simple/src/Layout.tsx
@@ -6,8 +6,9 @@ import {
InspectorButton,
TitlePortal,
useNotify,
+ useIsOffine,
} from 'react-admin';
-import { onlineManager, useQueryClient } from '@tanstack/react-query';
+import { useQueryClient } from '@tanstack/react-query';
import { Stack, Tooltip } from '@mui/material';
import OfflineIcon from '@mui/icons-material/SignalWifiConnectedNoInternet4';
import '../assets/app.css';
@@ -48,19 +49,6 @@ export const MyLayout = ({ children }) => (
>
);
-const useIsOffine = () => {
- const [isOnline, setIsOnline] = React.useState(onlineManager.isOnline());
-
- React.useEffect(() => {
- const handleChange = () => {
- setIsOnline(onlineManager.isOnline());
- };
- return onlineManager.subscribe(handleChange);
- }, []);
-
- return !isOnline;
-};
-
/**
* When react-query resumes persisted mutations through their default functions (provided in the getOfflineFirstQueryClient file) after the browser tab
* has been closed, it cannot handle their side effects unless we set up some defaults. In order to leverage the react-admin notification system
diff --git a/packages/ra-core/src/core/index.ts b/packages/ra-core/src/core/index.ts
index fb7b21aa759..543c0ca85a8 100644
--- a/packages/ra-core/src/core/index.ts
+++ b/packages/ra-core/src/core/index.ts
@@ -15,6 +15,7 @@ export * from './SourceContext';
export * from './useFirstResourceWithListAccess';
export * from './useGetResourceLabel';
export * from './useGetRecordRepresentation';
+export * from './useIsOffine';
export * from './useResourceDefinitionContext';
export * from './useResourceContext';
export * from './useResourceDefinition';
diff --git a/packages/ra-core/src/core/useIsOffine.ts b/packages/ra-core/src/core/useIsOffine.ts
new file mode 100644
index 00000000000..6e54c89ef1d
--- /dev/null
+++ b/packages/ra-core/src/core/useIsOffine.ts
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import { onlineManager } from '@tanstack/react-query';
+
+/**
+ * Hook to determine if the application is offline.
+ * It uses the onlineManager from react-query to check the online status.
+ * It returns true if the application is offline, false otherwise.
+ * @returns {boolean} - True if offline, false if online.
+ */
+export const useIsOffine = () => {
+ const [isOnline, setIsOnline] = React.useState(onlineManager.isOnline());
+
+ React.useEffect(() => {
+ const handleChange = () => {
+ setIsOnline(onlineManager.isOnline());
+ };
+ return onlineManager.subscribe(handleChange);
+ }, []);
+
+ return !isOnline;
+};
diff --git a/packages/ra-language-english/src/index.ts b/packages/ra-language-english/src/index.ts
index 3e0f0370f0e..389d4f150d2 100644
--- a/packages/ra-language-english/src/index.ts
+++ b/packages/ra-language-english/src/index.ts
@@ -174,6 +174,8 @@ const englishMessages: TranslationMessages = {
not_authorized: "You're not authorized to access this resource.",
application_update_available: 'A new version is available.',
offline: 'No connectivity. Could not fetch data.',
+ pending_operations:
+ 'There is a pending operation due to network not being available |||| There are %{smart_count} pending operations due to network not being available',
},
validation: {
required: 'Required',
diff --git a/packages/ra-language-french/src/index.ts b/packages/ra-language-french/src/index.ts
index 90a3e8de19a..06e14d7f8b8 100644
--- a/packages/ra-language-french/src/index.ts
+++ b/packages/ra-language-french/src/index.ts
@@ -182,6 +182,8 @@ const frenchMessages: TranslationMessages = {
"Vous n'êtes pas autorisé(e) à accéder à cette ressource.",
application_update_available: 'Une mise à jour est disponible.',
offline: 'Pas de connexion. Impossible de charger les données.',
+ pending_operations:
+ 'Il y a une opération en attente due à un problème de réseau |||| Il y a %{smart_count} opérations en attente dues à un problème de réseau',
},
validation: {
required: 'Ce champ est requis',
diff --git a/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx b/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx
index 99007ab5bf7..385d4eb4859 100644
--- a/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx
+++ b/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx
@@ -9,9 +9,11 @@ import {
} from '@mui/material/styles';
import clsx from 'clsx';
import CircularProgress from '@mui/material/CircularProgress';
-import { useLoading } from 'ra-core';
+import { Translate, useIsOffine, useLoading } from 'ra-core';
import { RefreshIconButton, type RefreshIconButtonProps } from '../button';
+import { Badge, Tooltip } from '@mui/material';
+import { useMutationState } from '@tanstack/react-query';
export const LoadingIndicator = (inProps: LoadingIndicatorProps) => {
const props = useThemeProps({
@@ -20,6 +22,12 @@ export const LoadingIndicator = (inProps: LoadingIndicatorProps) => {
});
const { className, onClick, sx, ...rest } = props;
const loading = useLoading();
+ const isOffline = useIsOffine();
+ const pendingMutations = useMutationState({
+ filters: {
+ status: 'pending',
+ },
+ });
const theme = useTheme();
return (
@@ -30,18 +38,51 @@ export const LoadingIndicator = (inProps: LoadingIndicatorProps) => {
}`}
onClick={onClick}
/>
- {loading && (
-
- )}
+ {loading ? (
+ isOffline ? (
+
+ {pendingMutations.length > 1
+ ? `There are ${pendingMutations.length} pending
+ operations due to network not being available`
+ : `There is a pending operation due to network not being available`}
+
+ }
+ >
+
+
+
+
+ ) : (
+
+ )
+ ) : null}
);
};
From a55d5ef6daf6d1b00ff6e02def6d5151816e7aa4 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Tue, 10 Jun 2025 12:03:06 +0200
Subject: [PATCH 40/58] Improve mutation mode selector
---
examples/simple/src/posts/PostCreate.tsx | 114 +++++++----------------
1 file changed, 36 insertions(+), 78 deletions(-)
diff --git a/examples/simple/src/posts/PostCreate.tsx b/examples/simple/src/posts/PostCreate.tsx
index f6dd0e6e1df..9db6b78e240 100644
--- a/examples/simple/src/posts/PostCreate.tsx
+++ b/examples/simple/src/posts/PostCreate.tsx
@@ -31,19 +31,14 @@ import {
import { useFormContext, useWatch } from 'react-hook-form';
import {
Button,
- ButtonGroup,
- ClickAwayListener,
Dialog,
DialogActions,
DialogContent,
- Grow,
+ Menu,
MenuItem,
- MenuList,
- Paper,
- Popper,
Stack,
} from '@mui/material';
-import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
+import MoreButton from '@mui/icons-material/MoreVert';
// Client side id generation. We start from 100 to avoid querying the post list to get the next id as we
// may be offline and accessing this page directly (without going through the list page first) which would
@@ -334,88 +329,51 @@ const MutationModesSelector = (props: {
setMutationMode: (mode: MutationMode) => void;
}) => {
const { setMutationMode, mutationMode } = props;
- const [open, setOpen] = React.useState(false);
- const anchorRef = React.useRef(null);
- const buttonRef = React.useRef(null);
-
- const handleMenuItemClick = (mutationMode: MutationMode) => {
- setOpen(false);
- setMutationMode(mutationMode);
+ const [anchorEl, setAnchorEl] = React.useState(null);
+ const open = Boolean(anchorEl);
+ const handleClick = (event: React.MouseEvent) => {
+ setAnchorEl(event.currentTarget);
};
-
- const handleToggle = () => {
- setOpen(prevOpen => !prevOpen);
+ const handleClose = () => {
+ setAnchorEl(null);
};
- const handleClose = (event: Event) => {
- if (
- anchorRef.current &&
- anchorRef.current.contains(event.target as HTMLElement)
- ) {
- return;
- }
-
- setOpen(false);
+ const handleMenuItemClick = (mutationMode: MutationMode) => {
+ setMutationMode(mutationMode);
};
return (
<>
- }
>
-
-
-
-
+
+ {mutationMode}
+
+ ))}
+
>
);
};
From 8384ca3af0cc95e8d9df92fae8eeb6750aec3fff Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Tue, 10 Jun 2025 12:09:27 +0200
Subject: [PATCH 41/58] Improve documentation
---
docs/DataTable.md | 4 ++-
docs/Datagrid.md | 18 +++++++++++--
docs/Edit.md | 6 +++--
docs/Show.md | 6 +++--
docs/SimpleList.md | 33 +++++++++++++++++++++++
docs/SingleFieldList.md | 58 ++++++++++++++++++++++++++++++++++++-----
6 files changed, 112 insertions(+), 13 deletions(-)
diff --git a/docs/DataTable.md b/docs/DataTable.md
index 0a02e153bfb..5e94245eaa0 100644
--- a/docs/DataTable.md
+++ b/docs/DataTable.md
@@ -757,7 +757,9 @@ export const PostList = () => (
## `offline`
-It's possible that a `` will have no records to display because of connectivity issues. In that case, `` will display a message indicating data couldn't be fetched.
+It's possible that a `` will have no records to display because of connectivity issues. In that case, `` will display the following message:
+
+> No connectivity. Could not fetch data.
You can customize this message via react-admin's [translation system](./Translation.md), by setting a custom translation for the `ra.notification.offline` key.
diff --git a/docs/Datagrid.md b/docs/Datagrid.md
index 9d26fd75edc..599c34b53be 100644
--- a/docs/Datagrid.md
+++ b/docs/Datagrid.md
@@ -643,9 +643,23 @@ export const PostList = () => (
## `offline`
-It's possible that a Datagrid will have no records to display because of connectivity issues. In that case, the Datagrid will display a message indicating data couldn't be fetched. This message is translatable and its key is `ra.notification.offline`.
+It's possible that a `` will have no records to display because of connectivity issues. In that case, `` will display the following message:
-You can customize the content to display by passing a component to the `offline` prop:
+> No connectivity. Could not fetch data.
+
+You can customize this message via react-admin's [translation system](./Translation.md), by setting a custom translation for the `ra.notification.offline` key.
+
+```tsx
+const messages = {
+ ra: {
+ notification: {
+ offline: "No network. Data couldn't be fetched.",
+ }
+ }
+}
+```
+
+If you need to go beyond text, pass a custom element as the `` prop:
```tsx
const CustomOffline = () => No network. Data couldn't be fetched.
;
diff --git a/docs/Edit.md b/docs/Edit.md
index 0b4bfed13db..e4a31e56d58 100644
--- a/docs/Edit.md
+++ b/docs/Edit.md
@@ -72,7 +72,7 @@ You can customize the `` component using the following props:
| `id` | | `string | number` | | the id of the record to edit |
| `mutationMode` | | `pessimistic | optimistic | undoable` | | switch to optimistic or pessimistic mutations (undoable by default) |
| `mutationOptions` | | `object` | | options for the `dataProvider.update()` call |
-| `offline` | | `ReactNode` | | The content rendered to render when data could not be fetched because of connectivity issues |
+| `offline` | | `ReactNode` | `` | The content rendered to render when data could not be fetched because of connectivity issues |
| `queryOptions` | | `object` | | options for the `dataProvider.getOne()` call |
| `redirect` | | `string | Function | false` | | change the redirect location after successful creation |
| `resource` | | `string` | | override the name of the resource to create |
@@ -493,7 +493,9 @@ The default `onError` function is:
## `offline`
-It's possible that an `` will have no record to display because of connectivity issues. In that case, `` will display a message indicating data couldn't be fetched.
+It's possible that a `` will have no records to display because of connectivity issues. In that case, `` will display the following message:
+
+> No connectivity. Could not fetch data.
You can customize this message via react-admin's [translation system](./Translation.md), by setting a custom translation for the `ra.notification.offline` key.
diff --git a/docs/Show.md b/docs/Show.md
index 3a806f3f3bd..fb79d8fd615 100644
--- a/docs/Show.md
+++ b/docs/Show.md
@@ -68,7 +68,7 @@ That's enough to display the post show view above.
| `disable Authentication` | Optional | `boolean` | | Set to `true` to disable the authentication check
| `empty WhileLoading` | Optional | `boolean` | | Set to `true` to return `null` while the show is loading
| `id` | Optional | `string | number` | | The record id. If not provided, it will be deduced from the URL
-| `offline` | Optional | `ReactNode` | | The content rendered to render when data could not be fetched because of connectivity issues
+| `offline` | | `ReactNode` | `` | The content rendered to render when data could not be fetched because of connectivity issues |
| `queryOptions` | Optional | `object` | | The options to pass to the `useQuery` hook
| `resource` | Optional | `string` | | The resource name, e.g. `posts`
| `sx` | Optional | `object` | | Override or extend the styles applied to the component
@@ -279,7 +279,9 @@ export const PostShow = () => (
## `offline`
-It's possible that a `` will have no record to display because of connectivity issues. In that case, `` will display a message indicating data couldn't be fetched.
+It's possible that a `` will have no records to display because of connectivity issues. In that case, `` will display the following message:
+
+> No connectivity. Could not fetch data.
You can customize this message via react-admin's [translation system](./Translation.md), by setting a custom translation for the `ra.notification.offline` key.
diff --git a/docs/SimpleList.md b/docs/SimpleList.md
index 98379633bde..07b908341ef 100644
--- a/docs/SimpleList.md
+++ b/docs/SimpleList.md
@@ -47,6 +47,7 @@ export const PostList = () => (
| `rowClick` | Optional |mixed | `"edit"` | The action to trigger when the user clicks on a row. |
| `leftAvatar` | Optional | function | | A function returning an `` component to display before the primary text. |
| `leftIcon` | Optional | function | | A function returning an `` component to display before the primary text. |
+| `offline` | Optional | Element | `` | The content rendered to render when data could not be fetched because of connectivity issues. |
| `rightAvatar` | Optional | function | | A function returning an `` component to display after the primary text. |
| `rightIcon` | Optional | function | | A function returning an `` component to display after the primary text. |
| `rowStyle` | Optional | function | | A function returning a style object to apply to each row. |
@@ -80,6 +81,38 @@ This prop should be a function returning an `` component. When present,
This prop should be a function returning an `` component. When present, the `` renders a `` before the ``
+## `offline`
+
+It's possible that a `` will have no records to display because of connectivity issues. In that case, `` will display the following message:
+
+> No connectivity. Could not fetch data.
+
+You can customize this message via react-admin's [translation system](./Translation.md), by setting a custom translation for the `ra.notification.offline` key.
+
+```tsx
+const messages = {
+ ra: {
+ notification: {
+ offline: "No network. Data couldn't be fetched.",
+ }
+ }
+}
+```
+
+If you need to go beyond text, pass a custom element as the `` prop:
+
+```tsx
+const Offline = () => (
+ No network. Data couldn't be fetched.
+);
+
+const BookList = () => (
+
+ } />
+
+);
+```
+
## `primaryText`
The `primaryText`, `secondaryText` and `tertiaryText` props can accept 4 types of values:
diff --git a/docs/SingleFieldList.md b/docs/SingleFieldList.md
index 4fc55b34917..55f5aa356e3 100644
--- a/docs/SingleFieldList.md
+++ b/docs/SingleFieldList.md
@@ -83,12 +83,13 @@ You can customize how each record is displayed by passing a Field component as c
`` accepts the following props:
-| Prop | Required | Type | Default | Description |
-| ----------- | -------- | ------------------------- | ------- | ----------------------------------------------- |
-| `children` | Optional | `ReactNode` | | React element to render for each record |
-| `empty` | Optional | `ReactNode` | | React element to display when the list is empty |
-| `linkType` | Optional | `'edit' | 'show' | false` | `edit` | The target of the link on each item |
-| `sx` | Optional | `object` | | The sx props of the Material UI Box component |
+| Prop | Required | Type | Default | Description |
+| ----------- | -------- | ------------------------- | ------------------------------ | ----------------------------------------------- |
+| `children` | Optional | `ReactNode` | | React element to render for each record |
+| `empty` | Optional | `ReactNode` | | React element to display when the list is empty |
+| `linkType` | Optional | `'edit' | 'show' | false` | `edit` | The target of the link on each item |
+| `offline` | Optional | Element | `` | The content rendered to render when data could not be fetched because of connectivity issues. |
+| `sx` | Optional | `object` | | The sx props of the Material UI Box component |
Additional props are passed down to the underlying [Material UI `` component](https://mui.com/material-ui/react-stack/).
@@ -133,6 +134,51 @@ The `` items link to the edition page by default. You can set t
* `linkType="show"`: links to the show page.
* `linkType={false}`: does not create any link.
+## `offline`
+
+It's possible that a `` will have no records to display because of connectivity issues. In that case, `` will display the following message:
+
+> No connectivity. Could not fetch data.
+
+You can customize this message via react-admin's [translation system](./Translation.md), by setting a custom translation for the `ra.notification.offline` key.
+
+```tsx
+const messages = {
+ ra: {
+ notification: {
+ offline: "No network. Data couldn't be fetched.",
+ }
+ }
+}
+```
+
+If you need to go beyond text, pass a custom element as the `` prop:
+
+```tsx
+import {
+ Show,
+ SimpleShowLayout,
+ TextField,
+ ReferenceArrayField,
+ SingleFieldList
+} from 'react-admin';
+
+const Offline = () => (
+ No network. Data couldn't be fetched.
+);
+
+const PostShow = () => (
+
+
+
+
+ } />
+
+
+
+);
+```
+
## `sx`: CSS API
The `` component accepts the usual `className` prop. You can also override the styles of the inner components thanks to the `sx` property. This property accepts the following subclasses:
From 02dbe24ef7b716be02e72a570aacfa561fdd4cce Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Fri, 20 Jun 2025 10:06:06 +0200
Subject: [PATCH 42/58] Improve notifications for offline mutations
---
examples/simple/src/i18n/en.ts | 6 ++++
examples/simple/src/i18n/fr.ts | 6 ++++
.../button/useDeleteWithConfirmController.tsx | 19 ++++++++----
.../button/useDeleteWithUndoController.tsx | 20 +++++++++----
.../controller/create/useCreateController.ts | 30 +++++++++++++------
.../src/controller/edit/useEditController.ts | 30 +++++++++++++------
.../button/BulkDeleteWithConfirmButton.tsx | 18 +++++++----
.../src/button/BulkDeleteWithUndoButton.tsx | 18 +++++++----
.../button/BulkUpdateWithConfirmButton.tsx | 30 +++++++++++++------
.../src/button/BulkUpdateWithUndoButton.tsx | 17 ++++++++---
.../src/button/UpdateWithConfirmButton.tsx | 30 ++++++++++++++-----
.../src/button/UpdateWithUndoButton.tsx | 30 ++++++++++++++-----
.../src/input/InPlaceEditor/InPlaceEditor.tsx | 30 +++++++++++++------
13 files changed, 206 insertions(+), 78 deletions(-)
diff --git a/examples/simple/src/i18n/en.ts b/examples/simple/src/i18n/en.ts
index d25cecf2569..16181cb0a7d 100644
--- a/examples/simple/src/i18n/en.ts
+++ b/examples/simple/src/i18n/en.ts
@@ -23,8 +23,14 @@ export const messages = {
},
notifications: {
created: 'Post created |||| %{smart_count} posts created',
+ pending_create:
+ 'Post will be created when your device will be online |||| %{smart_count} posts will be created when your device will be online',
updated: 'Post updated |||| %{smart_count} posts updated',
+ pending_update:
+ 'Post will be updated when your device will be online |||| %{smart_count} posts will be updated when your device will be online',
deleted: 'Post deleted |||| %{smart_count} posts deleted',
+ pending_delete:
+ 'Post will be deleted when your device will be online |||| %{smart_count} posts will be deleted when your device will be online',
},
},
comments: {
diff --git a/examples/simple/src/i18n/fr.ts b/examples/simple/src/i18n/fr.ts
index 91be5e8eac6..17d2155e9ed 100644
--- a/examples/simple/src/i18n/fr.ts
+++ b/examples/simple/src/i18n/fr.ts
@@ -39,10 +39,16 @@ export default {
},
notifications: {
created: 'Article créé |||| %{smart_count} articles créés',
+ pending_create:
+ "L'Article créé quand votre appareil sera connecté |||| %{smart_count} articles seront créés quand votre appareil sera connecté",
updated:
'Article mis à jour |||| %{smart_count} articles mis à jour',
+ pending_update:
+ "L'article sera mis à jour quand votre appareil sera connecté |||| %{smart_count} articles seront mis à jour quand votre appareil sera connecté",
deleted:
'Article supprimé |||| %{smart_count} articles supprimés',
+ pending_delete:
+ "L'article sera supprimé quand votre appareil sera connecté |||| %{smart_count} articles seront supprimés quand votre appareil sera connecté",
},
},
comments: {
diff --git a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx
index 15b72f8f8d4..a8acd403a6f 100644
--- a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx
+++ b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx
@@ -11,7 +11,7 @@ import { useUnselect } from '../../controller';
import { useRedirect, RedirectionSideEffect } from '../../routing';
import { useNotify } from '../../notification';
import { RaRecord, MutationMode, DeleteParams } from '../../types';
-import { useResourceContext } from '../../core';
+import { useIsOffine, useResourceContext } from '../../core';
import { useTranslate } from '../../i18n';
/**
@@ -90,6 +90,7 @@ const useDeleteWithConfirmController = <
const redirect = useRedirect();
const translate = useTranslate();
+ const isOffline = useIsOffine();
const [deleteOne, { isPending }] = useDelete(
resource,
undefined,
@@ -97,15 +98,21 @@ const useDeleteWithConfirmController = <
onSuccess: () => {
setOpen(false);
notify(
- successMessage ??
- `resources.${resource}.notifications.deleted`,
+ successMessage ?? isOffline
+ ? `resources.${resource}.notifications.pending_delete`
+ : `resources.${resource}.notifications.deleted`,
{
type: 'info',
messageArgs: {
smart_count: 1,
- _: translate('ra.notification.deleted', {
- smart_count: 1,
- }),
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_delete'
+ : 'ra.notification.deleted',
+ {
+ smart_count: 1,
+ }
+ ),
},
undoable: mutationMode === 'undoable',
}
diff --git a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx
index f04445c4b86..7d7367a1614 100644
--- a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx
+++ b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx
@@ -6,7 +6,7 @@ import { useUnselect } from '../../controller';
import { useRedirect, RedirectionSideEffect } from '../../routing';
import { useNotify } from '../../notification';
import { RaRecord, DeleteParams } from '../../types';
-import { useResourceContext } from '../../core';
+import { useIsOffine, useResourceContext } from '../../core';
import { useTranslate } from '../../i18n';
/**
@@ -63,21 +63,29 @@ const useDeleteWithUndoController = <
const unselect = useUnselect(resource);
const redirect = useRedirect();
const translate = useTranslate();
+
+ const isOffline = useIsOffine();
const [deleteOne, { isPending }] = useDelete(
resource,
undefined,
{
onSuccess: () => {
notify(
- successMessage ??
- `resources.${resource}.notifications.deleted`,
+ successMessage ?? isOffline
+ ? `resources.${resource}.notifications.pending_delete`
+ : `resources.${resource}.notifications.deleted`,
{
type: 'info',
messageArgs: {
smart_count: 1,
- _: translate('ra.notification.deleted', {
- smart_count: 1,
- }),
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_delete'
+ : 'ra.notification.deleted',
+ {
+ smart_count: 1,
+ }
+ ),
},
undoable: true,
}
diff --git a/packages/ra-core/src/controller/create/useCreateController.ts b/packages/ra-core/src/controller/create/useCreateController.ts
index b078f5976f1..7e5d9247117 100644
--- a/packages/ra-core/src/controller/create/useCreateController.ts
+++ b/packages/ra-core/src/controller/create/useCreateController.ts
@@ -20,6 +20,7 @@ import {
useResourceContext,
useResourceDefinition,
useGetResourceLabel,
+ useIsOffine,
} from '../../core';
/**
@@ -87,6 +88,7 @@ export const useCreateController = <
unregisterMutationMiddleware,
} = useMutationMiddlewares();
+ const isOffline = useIsOffine();
const [create, { isPending: saving }] = useCreate<
RecordType,
MutationOptionsError,
@@ -96,16 +98,26 @@ export const useCreateController = <
if (onSuccess) {
return onSuccess(data, variables, context);
}
- notify(`resources.${resource}.notifications.created`, {
- type: 'info',
- messageArgs: {
- smart_count: 1,
- _: translate(`ra.notification.created`, {
+ notify(
+ isOffline
+ ? `resources.${resource}.notifications.pending_create`
+ : `resources.${resource}.notifications.created`,
+ {
+ type: 'info',
+ messageArgs: {
smart_count: 1,
- }),
- },
- undoable: mutationMode === 'undoable',
- });
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_create'
+ : 'ra.notification.created',
+ {
+ smart_count: 1,
+ }
+ ),
+ },
+ undoable: mutationMode === 'undoable',
+ }
+ );
redirect(finalRedirectTo, resource, data.id, data);
},
onError: (error: MutationOptionsError, variables, context) => {
diff --git a/packages/ra-core/src/controller/edit/useEditController.ts b/packages/ra-core/src/controller/edit/useEditController.ts
index c53e7436eb3..a271a2c80eb 100644
--- a/packages/ra-core/src/controller/edit/useEditController.ts
+++ b/packages/ra-core/src/controller/edit/useEditController.ts
@@ -19,6 +19,7 @@ import {
useResourceContext,
useGetResourceLabel,
useGetRecordRepresentation,
+ useIsOffine,
} from '../../core';
import {
SaveContextValue,
@@ -153,6 +154,7 @@ export const useEditController = <
const recordCached = { id, previousData: record };
+ const isOffline = useIsOffine();
const [update, { isPending: saving }] = useUpdate(
resource,
recordCached,
@@ -161,16 +163,26 @@ export const useEditController = <
if (onSuccess) {
return onSuccess(data, variables, context);
}
- notify(`resources.${resource}.notifications.updated`, {
- type: 'info',
- messageArgs: {
- smart_count: 1,
- _: translate('ra.notification.updated', {
+ notify(
+ isOffline
+ ? `resources.${resource}.notifications.pending_update`
+ : `resources.${resource}.notifications.updated`,
+ {
+ type: 'info',
+ messageArgs: {
smart_count: 1,
- }),
- },
- undoable: mutationMode === 'undoable',
- });
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_update'
+ : 'ra.notification.updated',
+ {
+ smart_count: 1,
+ }
+ ),
+ },
+ undoable: mutationMode === 'undoable',
+ }
+ );
redirect(redirectTo, resource, data.id, data);
},
onError: (error, variables, context) => {
diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx
index 8e6a5d42551..73b4166a8d7 100644
--- a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx
+++ b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx
@@ -10,6 +10,7 @@ import {
import {
type MutationMode,
useDeleteMany,
+ useIsOffine,
useListContext,
useNotify,
useRefresh,
@@ -50,6 +51,7 @@ export const BulkDeleteWithConfirmButton = (
const resource = useResourceContext(props);
const refresh = useRefresh();
const translate = useTranslate();
+ const isOffline = useIsOffine();
const [deleteMany, { isPending }] = useDeleteMany(
resource,
{ ids: selectedIds, meta: mutationMeta },
@@ -57,15 +59,21 @@ export const BulkDeleteWithConfirmButton = (
onSuccess: () => {
refresh();
notify(
- successMessage ??
- `resources.${resource}.notifications.deleted`,
+ successMessage ?? isOffline
+ ? `resources.${resource}.notifications.pending_delete`
+ : `resources.${resource}.notifications.deleted`,
{
type: 'info',
messageArgs: {
smart_count: selectedIds.length,
- _: translate('ra.notification.deleted', {
- smart_count: selectedIds.length,
- }),
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_delete'
+ : 'ra.notification.deleted',
+ {
+ smart_count: selectedIds.length,
+ }
+ ),
},
undoable: mutationMode === 'undoable',
}
diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx
index bf215ba0dd7..215c2fcf052 100644
--- a/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx
+++ b/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx
@@ -7,6 +7,7 @@ import {
} from '@mui/material/styles';
import {
useDeleteMany,
+ useIsOffine,
useRefresh,
useNotify,
useResourceContext,
@@ -43,6 +44,7 @@ export const BulkDeleteWithUndoButton = (
const translate = useTranslate();
const [deleteMany, { isPending }] = useDeleteMany();
+ const isOffline = useIsOffine();
const handleClick = e => {
deleteMany(
resource,
@@ -50,15 +52,21 @@ export const BulkDeleteWithUndoButton = (
{
onSuccess: () => {
notify(
- successMessage ??
- `resources.${resource}.notifications.deleted`,
+ successMessage ?? isOffline
+ ? `resources.${resource}.notifications.pending_delete`
+ : `resources.${resource}.notifications.deleted`,
{
type: 'info',
messageArgs: {
smart_count: selectedIds.length,
- _: translate('ra.notification.deleted', {
- smart_count: selectedIds.length,
- }),
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_delete'
+ : 'ra.notification.deleted',
+ {
+ smart_count: selectedIds.length,
+ }
+ ),
},
undoable: true,
}
diff --git a/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx
index a32df5fe412..207a51013bd 100644
--- a/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx
+++ b/packages/ra-ui-materialui/src/button/BulkUpdateWithConfirmButton.tsx
@@ -16,6 +16,7 @@ import {
type MutationMode,
type RaRecord,
type UpdateManyParams,
+ useIsOffine,
} from 'ra-core';
import { Confirm } from '../layout';
@@ -36,6 +37,7 @@ export const BulkUpdateWithConfirmButton = (
const unselectAll = useUnselectAll(resource);
const [isOpen, setOpen] = useState(false);
const { selectedIds } = useListContext();
+ const isOffline = useIsOffine();
const {
confirmTitle = 'ra.message.bulk_update_title',
@@ -46,16 +48,26 @@ export const BulkUpdateWithConfirmButton = (
mutationMode = 'pessimistic',
onClick,
onSuccess = () => {
- notify(`resources.${resource}.notifications.updated`, {
- type: 'info',
- messageArgs: {
- smart_count: selectedIds.length,
- _: translate('ra.notification.updated', {
+ notify(
+ isOffline
+ ? `resources.${resource}.notifications.pending_update`
+ : `resources.${resource}.notifications.updated`,
+ {
+ type: 'info',
+ messageArgs: {
smart_count: selectedIds.length,
- }),
- },
- undoable: mutationMode === 'undoable',
- });
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_update'
+ : 'ra.notification.updated',
+ {
+ smart_count: selectedIds.length,
+ }
+ ),
+ },
+ undoable: mutationMode === 'undoable',
+ }
+ );
unselectAll();
setOpen(false);
},
diff --git a/packages/ra-ui-materialui/src/button/BulkUpdateWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/BulkUpdateWithUndoButton.tsx
index cd5fe33dcd9..896c0c75c00 100644
--- a/packages/ra-ui-materialui/src/button/BulkUpdateWithUndoButton.tsx
+++ b/packages/ra-ui-materialui/src/button/BulkUpdateWithUndoButton.tsx
@@ -15,6 +15,7 @@ import {
type RaRecord,
type UpdateManyParams,
useTranslate,
+ useIsOffine,
} from 'ra-core';
import type { UseMutationOptions } from '@tanstack/react-query';
@@ -34,6 +35,7 @@ export const BulkUpdateWithUndoButton = (
const unselectAll = useUnselectAll(resource);
const refresh = useRefresh();
const translate = useTranslate();
+ const isOffline = useIsOffine();
const {
data,
@@ -43,14 +45,21 @@ export const BulkUpdateWithUndoButton = (
onClick,
onSuccess = () => {
notify(
- successMessage ?? `resources.${resource}.notifications.updated`,
+ successMessage ?? isOffline
+ ? `resources.${resource}.notifications.pending_update`
+ : `resources.${resource}.notifications.updated`,
{
type: 'info',
messageArgs: {
smart_count: selectedIds.length,
- _: translate('ra.notification.updated', {
- smart_count: selectedIds.length,
- }),
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_update'
+ : 'ra.notification.updated',
+ {
+ smart_count: selectedIds.length,
+ }
+ ),
},
undoable: true,
}
diff --git a/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.tsx
index 0055d86b252..607af222c76 100644
--- a/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.tsx
+++ b/packages/ra-ui-materialui/src/button/UpdateWithConfirmButton.tsx
@@ -17,6 +17,7 @@ import {
useRecordContext,
useUpdate,
useGetRecordRepresentation,
+ useIsOffine,
} from 'ra-core';
import { Confirm } from '../layout';
@@ -36,6 +37,7 @@ export const UpdateWithConfirmButton = (
const resource = useResourceContext(props);
const [isOpen, setOpen] = useState(false);
const record = useRecordContext(props);
+ const isOffline = useIsOffine();
const {
confirmTitle: confirmTitleProp,
@@ -51,14 +53,26 @@ export const UpdateWithConfirmButton = (
const {
meta: mutationMeta,
onSuccess = () => {
- notify(`resources.${resource}.notifications.updated`, {
- type: 'info',
- messageArgs: {
- smart_count: 1,
- _: translate('ra.notification.updated', { smart_count: 1 }),
- },
- undoable: mutationMode === 'undoable',
- });
+ notify(
+ isOffline
+ ? `resources.${resource}.notifications.pending_update`
+ : `resources.${resource}.notifications.updated`,
+ {
+ type: 'info',
+ messageArgs: {
+ smart_count: 1,
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_update'
+ : 'ra.notification.updated',
+ {
+ smart_count: 1,
+ }
+ ),
+ },
+ undoable: mutationMode === 'undoable',
+ }
+ );
},
onError = (error: Error | string) => {
notify(
diff --git a/packages/ra-ui-materialui/src/button/UpdateWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/UpdateWithUndoButton.tsx
index 331f9b731fe..eb469141689 100644
--- a/packages/ra-ui-materialui/src/button/UpdateWithUndoButton.tsx
+++ b/packages/ra-ui-materialui/src/button/UpdateWithUndoButton.tsx
@@ -14,6 +14,7 @@ import {
useUpdate,
type UpdateParams,
useTranslate,
+ useIsOffine,
} from 'ra-core';
import type { UseMutationOptions } from '@tanstack/react-query';
@@ -29,6 +30,7 @@ export const UpdateWithUndoButton = (inProps: UpdateWithUndoButtonProps) => {
const resource = useResourceContext(props);
const refresh = useRefresh();
const translate = useTranslate();
+ const isOffline = useIsOffine();
const {
data,
@@ -44,14 +46,26 @@ export const UpdateWithUndoButton = (inProps: UpdateWithUndoButtonProps) => {
const {
meta: mutationMeta,
onSuccess = () => {
- notify(`resources.${resource}.notifications.updated`, {
- type: 'info',
- messageArgs: {
- smart_count: 1,
- _: translate('ra.notification.updated', { smart_count: 1 }),
- },
- undoable: true,
- });
+ notify(
+ isOffline
+ ? `resources.${resource}.notifications.pending_update`
+ : `resources.${resource}.notifications.updated`,
+ {
+ type: 'info',
+ messageArgs: {
+ smart_count: 1,
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_update'
+ : 'ra.notification.updated',
+ {
+ smart_count: 1,
+ }
+ ),
+ },
+ undoable: true,
+ }
+ );
},
onError = (error: Error | string) => {
notify(
diff --git a/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.tsx b/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.tsx
index 53ce1d9130f..1704f6c190d 100644
--- a/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.tsx
+++ b/packages/ra-ui-materialui/src/input/InPlaceEditor/InPlaceEditor.tsx
@@ -10,6 +10,7 @@ import {
RecordContextProvider,
type UseUpdateOptions,
type RaRecord,
+ useIsOffine,
} from 'ra-core';
import isEqual from 'lodash/isEqual';
import { styled } from '@mui/material/styles';
@@ -130,22 +131,33 @@ export const InPlaceEditor = <
const notify = useNotify();
const translate = useTranslate();
const [update] = useUpdate();
+ const isOffline = useIsOffine();
const {
meta: mutationMeta,
onSuccess = () => {
dispatch({ type: 'success' });
if (mutationMode !== 'undoable' && !notifyOnSuccess) return;
- notify(`resources.${resource}.notifications.updated`, {
- type: 'info',
- messageArgs: {
- smart_count: 1,
- _: translate('ra.notification.updated', {
+ notify(
+ isOffline
+ ? `resources.${resource}.notifications.pending_update`
+ : `resources.${resource}.notifications.updated`,
+ {
+ type: 'info',
+ messageArgs: {
smart_count: 1,
- }),
- },
- undoable: mutationMode === 'undoable',
- });
+ _: translate(
+ isOffline
+ ? 'ra.notification.pending_update'
+ : 'ra.notification.updated',
+ {
+ smart_count: 1,
+ }
+ ),
+ },
+ undoable: mutationMode === 'undoable',
+ }
+ );
},
onError = error => {
notify('ra.notification.http_error', {
From b648d085eb5ca16bb3adf43823ed4c487d210f55 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Wed, 25 Jun 2025 16:12:35 +0200
Subject: [PATCH 43/58] Fix delete mutations success message handling
---
.../controller/button/useDeleteWithConfirmController.tsx | 8 +++++---
.../src/controller/button/useDeleteWithUndoController.tsx | 8 +++++---
.../src/button/BulkDeleteWithConfirmButton.tsx | 8 +++++---
.../src/button/BulkDeleteWithUndoButton.tsx | 8 +++++---
4 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx
index a8acd403a6f..bb49bfbcdff 100644
--- a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx
+++ b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx
@@ -98,9 +98,11 @@ const useDeleteWithConfirmController = <
onSuccess: () => {
setOpen(false);
notify(
- successMessage ?? isOffline
- ? `resources.${resource}.notifications.pending_delete`
- : `resources.${resource}.notifications.deleted`,
+ successMessage != null
+ ? successMessage
+ : isOffline
+ ? `resources.${resource}.notifications.pending_delete`
+ : `resources.${resource}.notifications.deleted`,
{
type: 'info',
messageArgs: {
diff --git a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx
index 7d7367a1614..2cee6be3cf0 100644
--- a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx
+++ b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx
@@ -71,9 +71,11 @@ const useDeleteWithUndoController = <
{
onSuccess: () => {
notify(
- successMessage ?? isOffline
- ? `resources.${resource}.notifications.pending_delete`
- : `resources.${resource}.notifications.deleted`,
+ successMessage != null
+ ? successMessage
+ : isOffline
+ ? `resources.${resource}.notifications.pending_delete`
+ : `resources.${resource}.notifications.deleted`,
{
type: 'info',
messageArgs: {
diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx
index 477aaf4972c..bdc1c4d89c4 100644
--- a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx
+++ b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx
@@ -59,9 +59,11 @@ export const BulkDeleteWithConfirmButton = (
onSuccess: () => {
refresh();
notify(
- successMessage ?? isOffline
- ? `resources.${resource}.notifications.pending_delete`
- : `resources.${resource}.notifications.deleted`,
+ successMessage != null
+ ? successMessage
+ : isOffline
+ ? `resources.${resource}.notifications.pending_delete`
+ : `resources.${resource}.notifications.deleted`,
{
type: 'info',
messageArgs: {
diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx
index ff1fe48ef7a..7f2e258589a 100644
--- a/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx
+++ b/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx
@@ -52,9 +52,11 @@ export const BulkDeleteWithUndoButton = (
{
onSuccess: () => {
notify(
- successMessage ?? isOffline
- ? `resources.${resource}.notifications.pending_delete`
- : `resources.${resource}.notifications.deleted`,
+ successMessage != null
+ ? successMessage
+ : isOffline
+ ? `resources.${resource}.notifications.pending_delete`
+ : `resources.${resource}.notifications.deleted`,
{
type: 'info',
messageArgs: {
From 929d606fd85aea01308ccb66b1a883057907cea9 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Wed, 25 Jun 2025 16:12:59 +0200
Subject: [PATCH 44/58] Fix ReferenceOneField empty case handling
---
.../src/field/ReferenceOneField.tsx | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx b/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx
index fb3ec2a1848..f895eb4d322 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceOneField.tsx
@@ -95,19 +95,15 @@ export const ReferenceOneField = <
) : null;
if (
- !record &&
- !controllerProps.isPending &&
- !controllerProps.isPaused &&
- controllerProps.referenceRecord == null
+ !record ||
+ (!controllerProps.isPending &&
+ !controllerProps.isPaused &&
+ controllerProps.referenceRecord == null)
) {
return empty;
}
- if (
- !record &&
- controllerProps.isPaused &&
- controllerProps.referenceRecord == null
- ) {
+ if (controllerProps.isPaused && controllerProps.referenceRecord == null) {
return offline;
}
From 1154f17a16efbc51edd6b7dd2f96bdeeb80bbaaf Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 14:35:45 +0200
Subject: [PATCH 45/58] Fix simple example dependencies
---
examples/simple/package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/examples/simple/package.json b/examples/simple/package.json
index e84203323a3..3dd8e9dd21a 100644
--- a/examples/simple/package.json
+++ b/examples/simple/package.json
@@ -12,10 +12,10 @@
"dependencies": {
"@mui/icons-material": "^5.16.12",
"@mui/material": "^5.16.12",
- "@tanstack/query-sync-storage-persister": "5.47.0",
+ "@tanstack/query-sync-storage-persister": "^5.47.0",
"@tanstack/react-query": "^5.21.7",
"@tanstack/react-query-devtools": "^5.21.7",
- "@tanstack/react-query-persist-client": "5.47.0",
+ "@tanstack/react-query-persist-client": "^5.47.0",
"jsonexport": "^3.2.0",
"lodash": "~4.17.5",
"ra-data-fakerest": "^5.9.0",
From 5b7f83a2d82d90714dc927278ad0d936fecc47ef Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 15:21:43 +0200
Subject: [PATCH 46/58] Fix JSDoc examples
---
.../addOfflineSupportToQueryClient.ts | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/packages/ra-core/src/dataProvider/addOfflineSupportToQueryClient.ts b/packages/ra-core/src/dataProvider/addOfflineSupportToQueryClient.ts
index c5b41c0bf78..3916a6a4037 100644
--- a/packages/ra-core/src/dataProvider/addOfflineSupportToQueryClient.ts
+++ b/packages/ra-core/src/dataProvider/addOfflineSupportToQueryClient.ts
@@ -9,10 +9,10 @@ import type { DataProvider } from '../types';
*
* @example Adding offline support for the default mutations
* // in src/App.tsx
- * import { Admin, Resource, addOfflineSupportToQueryClient, reactAdminMutations } from 'react-admin';
+ * import { addOfflineSupportToQueryClient } from 'react-admin';
+ * import { QueryClient } from '@tanstack/react-query';
* import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
* import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
- * import { queryClient } from './queryClient';
* import { dataProvider } from './dataProvider';
* import { posts } from './posts';
* import { comments } from './comments';
@@ -21,11 +21,10 @@ import type { DataProvider } from '../types';
* storage: window.localStorage,
* });
*
- * addOfflineSupportToQueryClient({
- * queryClient,
+ * const queryClient = addOfflineSupportToQueryClient({
+ * queryClient: new QueryClient(),
* dataProvider,
* resources: ['posts', 'comments'],
- * mutations: [...reactAdminMutations, 'myCustomMutation'],
* });
*
* const App = () => (
@@ -46,10 +45,10 @@ import type { DataProvider } from '../types';
*
* @example Adding offline support with custom mutations
* // in src/App.tsx
- * import { addOfflineSupportToQueryClient } from 'react-admin';
+ * import { Admin, Resource, addOfflineSupportToQueryClient, reactAdminMutations } from 'react-admin';
+ * import { QueryClient } from '@tanstack/react-query';
* import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
* import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
- * import { queryClient } from './queryClient';
* import { dataProvider } from './dataProvider';
* import { posts } from './posts';
* import { comments } from './comments';
@@ -58,10 +57,11 @@ import type { DataProvider } from '../types';
* storage: window.localStorage,
* });
*
- * addOfflineSupportToQueryClient({
- * queryClient,
+ * const queryClient = addOfflineSupportToQueryClient({
+ * queryClient: new QueryClient(),
* dataProvider,
* resources: ['posts', 'comments'],
+ * mutations: [...reactAdminMutations, 'myCustomMutation'],
* });
*
* const App = () => (
From d114f96fd0c67489eb36c9692b8b093af6c14e55 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 15:22:58 +0200
Subject: [PATCH 47/58] Remove unnecessary CSS classes
---
packages/ra-ui-materialui/src/detail/EditView.tsx | 1 -
packages/ra-ui-materialui/src/detail/ShowView.tsx | 1 -
2 files changed, 2 deletions(-)
diff --git a/packages/ra-ui-materialui/src/detail/EditView.tsx b/packages/ra-ui-materialui/src/detail/EditView.tsx
index 81279983cd4..6573a4adec5 100644
--- a/packages/ra-ui-materialui/src/detail/EditView.tsx
+++ b/packages/ra-ui-materialui/src/detail/EditView.tsx
@@ -93,7 +93,6 @@ export const EditClasses = {
main: `${PREFIX}-main`,
noActions: `${PREFIX}-noActions`,
card: `${PREFIX}-card`,
- offline: `${PREFIX}-offline`,
};
const Root = styled('div', {
diff --git a/packages/ra-ui-materialui/src/detail/ShowView.tsx b/packages/ra-ui-materialui/src/detail/ShowView.tsx
index 21920f58734..aae98b82c0a 100644
--- a/packages/ra-ui-materialui/src/detail/ShowView.tsx
+++ b/packages/ra-ui-materialui/src/detail/ShowView.tsx
@@ -88,7 +88,6 @@ export const ShowClasses = {
main: `${PREFIX}-main`,
noActions: `${PREFIX}-noActions`,
card: `${PREFIX}-card`,
- offline: `${PREFIX}-offline`,
};
const Root = styled('div', {
From 30a5ec05f2fd5230704f55a2d4b338b4e8f9dd70 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 15:24:11 +0200
Subject: [PATCH 48/58] Fix Offline types
---
packages/ra-ui-materialui/src/Offline.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/ra-ui-materialui/src/Offline.tsx b/packages/ra-ui-materialui/src/Offline.tsx
index d80766bed7a..fc6a711f189 100644
--- a/packages/ra-ui-materialui/src/Offline.tsx
+++ b/packages/ra-ui-materialui/src/Offline.tsx
@@ -72,19 +72,19 @@ const Root = styled(Alert, {
declare module '@mui/material/styles' {
interface ComponentNameToClassKey {
- RaOffline: 'root';
+ [PREFIX]: 'root';
}
interface ComponentsPropsList {
- RaOffline: Partial;
+ [PREFIX]: Partial;
}
interface Components {
- RaOffline?: {
- defaultProps?: ComponentsPropsList['RaOffline'];
+ [PREFIX]?: {
+ defaultProps?: ComponentsPropsList[typeof PREFIX];
styleOverrides?: ComponentsOverrides<
Omit
- >['RaOffline'];
+ >[typeof PREFIX];
};
}
}
From 68d00a72d09311fafd202d85199652da82f20228 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 15:25:01 +0200
Subject: [PATCH 49/58] Fix LoadingIndicator
---
packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx b/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx
index 385d4eb4859..4bde713a0e3 100644
--- a/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx
+++ b/packages/ra-ui-materialui/src/layout/LoadingIndicator.tsx
@@ -44,7 +44,9 @@ export const LoadingIndicator = (inProps: LoadingIndicatorProps) => {
title={
{pendingMutations.length > 1
? `There are ${pendingMutations.length} pending
From ed0f0dfecfcdd06a2ae8b720fb7543c0e6cc93df Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 15:55:03 +0200
Subject: [PATCH 50/58] Fix SingleFieldList story
---
packages/ra-ui-materialui/src/list/SingleFieldList.stories.tsx | 2 +-
packages/ra-ui-materialui/src/list/SingleFieldList.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/ra-ui-materialui/src/list/SingleFieldList.stories.tsx b/packages/ra-ui-materialui/src/list/SingleFieldList.stories.tsx
index 0d308657a71..5b20cce8548 100644
--- a/packages/ra-ui-materialui/src/list/SingleFieldList.stories.tsx
+++ b/packages/ra-ui-materialui/src/list/SingleFieldList.stories.tsx
@@ -128,7 +128,7 @@ export const Loading = () => (
);
export const Offline = () => (
-
+
);
diff --git a/packages/ra-ui-materialui/src/list/SingleFieldList.tsx b/packages/ra-ui-materialui/src/list/SingleFieldList.tsx
index c85b3a865ef..96b33f9c194 100644
--- a/packages/ra-ui-materialui/src/list/SingleFieldList.tsx
+++ b/packages/ra-ui-materialui/src/list/SingleFieldList.tsx
@@ -90,7 +90,7 @@ export const SingleFieldList = (inProps: SingleFieldListProps) => {
return null;
}
- if (isPaused && (isPlaceholderData || data == null)) {
+ if (isPaused && (isPlaceholderData || total == null)) {
if (offline) {
return offline;
}
From d0586a1e9fbc0f7a9ccbbfdf37198437979361ae Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 16:08:06 +0200
Subject: [PATCH 51/58] Fix ReferenceInput offline detection
---
.../input/useReferenceInputController.ts | 37 +++++++++++++------
1 file changed, 25 insertions(+), 12 deletions(-)
diff --git a/packages/ra-core/src/controller/input/useReferenceInputController.ts b/packages/ra-core/src/controller/input/useReferenceInputController.ts
index 1bfbd19f2f8..e183c62c3bb 100644
--- a/packages/ra-core/src/controller/input/useReferenceInputController.ts
+++ b/packages/ra-core/src/controller/input/useReferenceInputController.ts
@@ -79,7 +79,7 @@ export const useReferenceInputController = (
// fetch possible values
const {
- data: possibleValuesData = [],
+ data: possibleValuesData,
total,
pageInfo,
isFetching: isFetchingPossibleValues,
@@ -143,17 +143,30 @@ export const useReferenceInputController = (
}, [currentReferenceRecord]);
// add current value to possible sources
- let finalData: RecordType[], finalTotal: number | undefined;
- if (
- !referenceRecord ||
- possibleValuesData.find(record => record.id === referenceRecord.id)
- ) {
- finalData = possibleValuesData;
- finalTotal = total;
- } else {
- finalData = [referenceRecord, ...possibleValuesData];
- finalTotal = total == null ? undefined : total + 1;
- }
+ const { finalData, finalTotal } = useMemo(() => {
+ if (isPaused && possibleValuesData == null) {
+ return {
+ finalData: null,
+ finalTotal: null,
+ };
+ }
+ if (
+ !referenceRecord ||
+ (possibleValuesData ?? []).find(
+ record => record.id === referenceRecord.id
+ )
+ ) {
+ return {
+ finalData: possibleValuesData,
+ finalTotal: total,
+ };
+ } else {
+ return {
+ finalData: [referenceRecord, ...(possibleValuesData ?? [])],
+ finalTotal: total == null ? undefined : total + 1,
+ };
+ }
+ }, [isPaused, referenceRecord, possibleValuesData, total]);
const refetch = useCallback(() => {
refetchGetList();
From b03dfb4b3fdc822ef85a13d56c31c6318fc1b13d Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 17:04:01 +0200
Subject: [PATCH 52/58] Fix reference fields and inputs
---
.../input/useReferenceArrayInputController.ts | 3 +
.../src/form/choices/ChoicesContext.ts | 1 +
.../src/form/choices/useChoicesContext.ts | 4 +
packages/ra-ui-materialui/src/Labeled.tsx | 24 +++---
.../src/field/ReferenceArrayField.stories.tsx | 8 +-
.../src/field/ReferenceManyField.stories.tsx | 36 ++++++++-
.../src/input/AutocompleteInput.tsx | 34 +++++++--
.../src/input/ReferenceArrayInput.stories.tsx | 33 ++++-----
.../src/input/ReferenceArrayInput.tsx | 16 ++--
.../src/input/ReferenceInput.stories.tsx | 74 ++++++++++++++-----
.../src/input/ReferenceInput.tsx | 16 ++--
11 files changed, 174 insertions(+), 75 deletions(-)
diff --git a/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts b/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts
index 1e18086911d..fa883c58b21 100644
--- a/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts
+++ b/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts
@@ -63,6 +63,7 @@ export const useReferenceArrayInputController = <
isFetching: isFetchingGetMany,
isPaused: isPausedGetMany,
isPending: isPendingGetMany,
+ isPlaceholderData: isPlaceholderDataGetMany,
refetch: refetchGetMany,
} = useGetManyAggregate(
reference,
@@ -102,6 +103,7 @@ export const useReferenceArrayInputController = <
isFetching: isFetchingGetList,
isPaused: isPausedGetList,
isPending: isPendingGetList,
+ isPlaceholderData: isPlaceholderDataGetList,
refetch: refetchGetMatching,
} = useGetList(
reference,
@@ -157,6 +159,7 @@ export const useReferenceArrayInputController = <
isLoading: isLoadingGetMany || isLoadingGetList,
isPaused: isPausedGetMany || isPausedGetList,
isPending: isPendingGetMany || isPendingGetList,
+ isPlaceholderData: isPlaceholderDataGetMany || isPlaceholderDataGetList,
page: params.page,
perPage: params.perPage,
refetch,
diff --git a/packages/ra-core/src/form/choices/ChoicesContext.ts b/packages/ra-core/src/form/choices/ChoicesContext.ts
index 1821769559a..a420a6351b0 100644
--- a/packages/ra-core/src/form/choices/ChoicesContext.ts
+++ b/packages/ra-core/src/form/choices/ChoicesContext.ts
@@ -21,6 +21,7 @@ export type ChoicesContextBaseValue = {
isFetching: boolean;
isLoading: boolean;
isPaused: boolean;
+ isPlaceholderData: boolean;
page: number;
perPage: number;
refetch: (() => void) | UseGetListHookValue['refetch'];
diff --git a/packages/ra-core/src/form/choices/useChoicesContext.ts b/packages/ra-core/src/form/choices/useChoicesContext.ts
index e7a31c13684..7528ee5ef1b 100644
--- a/packages/ra-core/src/form/choices/useChoicesContext.ts
+++ b/packages/ra-core/src/form/choices/useChoicesContext.ts
@@ -21,6 +21,8 @@ export const useChoicesContext = (
isLoading: options.isLoading ?? false,
isPending: options.isPending ?? false,
isFetching: options.isFetching ?? false,
+ isPaused: options.isPaused ?? false,
+ isPlaceholderData: options.isPlaceholderData ?? false,
error: options.error,
// When not in a ChoicesContext, paginating does not make sense (e.g. AutocompleteInput).
perPage: Infinity,
@@ -44,6 +46,8 @@ export const useChoicesContext = (
isLoading: list.isLoading ?? false, // we must take the one for useList, otherwise the loading state isn't synchronized with the data
isPending: list.isPending ?? false, // same
isFetching: list.isFetching ?? false, // same
+ isPaused: list.isPaused ?? false, // same
+ isPlaceholderData: list.isPlaceholderData ?? false, // same
page: options.page ?? list.page,
perPage: options.perPage ?? list.perPage,
refetch: options.refetch ?? list.refetch,
diff --git a/packages/ra-ui-materialui/src/Labeled.tsx b/packages/ra-ui-materialui/src/Labeled.tsx
index 8b188280690..416080ac252 100644
--- a/packages/ra-ui-materialui/src/Labeled.tsx
+++ b/packages/ra-ui-materialui/src/Labeled.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import type { ElementType, ReactElement } from 'react';
+import type { ElementType, ReactElement, ReactNode } from 'react';
import {
Stack,
type StackProps,
@@ -45,6 +45,14 @@ export const Labeled = (inProps: LabeledProps) => {
...rest
} = props;
+ const childrenProps = React.isValidElement(children) ? children.props : {};
+ const isLabeled = React.isValidElement(children)
+ ? // @ts-ignore
+ children.type?.displayName === 'Labeled'
+ : false;
+ const shouldAddLabel =
+ label !== false && childrenProps.label !== false && !isLabeled;
+
return (
{
})}
{...rest}
>
- {label !== false &&
- children.props.label !== false &&
- typeof children.type !== 'string' &&
- // @ts-ignore
- children.type?.displayName !== 'Labeled' &&
- // @ts-ignore
- children.type?.displayName !== 'Labeled' ? (
+ {shouldAddLabel ? (
{
{...TypographyProps}
>
@@ -90,7 +92,7 @@ export const Labeled = (inProps: LabeledProps) => {
Labeled.displayName = 'Labeled';
export interface LabeledProps extends StackProps {
- children: ReactElement;
+ children: ReactNode;
className?: string;
color?:
| ResponsiveStyleValue
diff --git a/packages/ra-ui-materialui/src/field/ReferenceArrayField.stories.tsx b/packages/ra-ui-materialui/src/field/ReferenceArrayField.stories.tsx
index 183d1f2c255..ed00e3318aa 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceArrayField.stories.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceArrayField.stories.tsx
@@ -108,7 +108,11 @@ export const Offline = () => (
);
export const OfflineWithChildren = () => (
-
+ englishMessages)}
+ defaultTheme="light"
+ >
@@ -117,11 +121,13 @@ export const OfflineWithChildren = () => (
+
diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyField.stories.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyField.stories.tsx
index af67b19b2dc..7a572bfe383 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceManyField.stories.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceManyField.stories.tsx
@@ -8,7 +8,7 @@ import {
} from 'ra-core';
import { Admin, ListGuesser, Resource } from 'react-admin';
import type { AdminProps } from 'react-admin';
-import { ThemeProvider, Box, Stack } from '@mui/material';
+import { ThemeProvider, Box, Stack, Typography } from '@mui/material';
import { createTheme } from '@mui/material/styles';
import fakeDataProvider from 'ra-data-fakerest';
import polyglotI18nProvider from 'ra-i18n-polyglot';
@@ -123,6 +123,40 @@ export const Basic = () => (
);
+const LoadChildrenOnDemand = ({ children }: { children: React.ReactNode }) => {
+ const [showChildren, setShowChildren] = React.useState(false);
+ const handleClick = () => {
+ setShowChildren(true);
+ };
+ return showChildren ? (
+ children
+ ) : (
+
+
+ Don't forget to go offline first
+
+
+
+ );
+};
+
+export const Offline = () => (
+
+
+ }
+ perPage={5}
+ >
+
+
+
+
+
+
+);
+
export const WithSingleFieldList = () => (
diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
index 425041913f4..7c78f9994ce 100644
--- a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
+++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
@@ -48,6 +48,7 @@ import {
import type { CommonInputProps } from './CommonInputProps';
import { InputHelperText } from './InputHelperText';
import { sanitizeInputRestProps } from './sanitizeInputRestProps';
+import { Offline } from '../Offline';
const defaultFilterOptions = createFilterOptions();
@@ -161,6 +162,7 @@ export const AutocompleteInput = <
isRequired: isRequiredOverride,
label,
limitChoicesToValue,
+ loadingText = 'ra.message.loading',
matchSuggestion,
margin,
fieldState: fieldStateOverride,
@@ -168,6 +170,7 @@ export const AutocompleteInput = <
formState: formStateOverride,
multiple = false,
noOptionsText,
+ offline = defaultOffline,
onBlur,
onChange,
onCreate,
@@ -197,6 +200,8 @@ export const AutocompleteInput = <
const {
allChoices,
isPending,
+ isPaused,
+ isPlaceholderData,
error: fetchError,
resource,
source,
@@ -610,12 +615,22 @@ If you provided a React element for the optionText prop, you must also provide t
const renderHelperText = !!fetchError || helperText !== false || invalid;
const handleInputRef = useForkRef(field.ref, TextFieldProps?.inputRef);
+
return (
<>
any;
inputText?: (option: any) => string;
+ offline?: ReactNode;
onChange?: (
// We can't know upfront what the value type will be
value: Multiple extends true ? any[] : any,
@@ -912,6 +931,7 @@ const areSelectedItemsEqual = (
};
const DefaultFilterToQuery = searchText => ({ q: searchText });
+const defaultOffline = ;
declare module '@mui/material/styles' {
interface ComponentNameToClassKey {
diff --git a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.stories.tsx b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.stories.tsx
index 24f18ee9e59..8205dcdb5a4 100644
--- a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.stories.tsx
+++ b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.stories.tsx
@@ -1,10 +1,5 @@
import * as React from 'react';
-import {
- DataProvider,
- Form,
- testDataProvider,
- TestMemoryRouter,
-} from 'ra-core';
+import { DataProvider, Form, TestMemoryRouter } from 'ra-core';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';
import { Admin, Resource } from 'react-admin';
@@ -29,23 +24,21 @@ const tags = [
{ id: 2, name: 'Design' },
{ id: 3, name: 'Painting' },
{ id: 4, name: 'Photography' },
+ { id: 5, name: 'Sculpture' },
+ { id: 6, name: 'Urbanism' },
+ { id: 7, name: 'Video' },
+ { id: 8, name: 'Web' },
+ { id: 9, name: 'Writing' },
+ { id: 10, name: 'Other' },
];
-const dataProvider = testDataProvider({
- // @ts-ignore
- getList: () =>
- Promise.resolve({
- data: tags,
- total: tags.length,
- }),
- // @ts-ignore
- getMany: (resource, params) => {
- console.log('getMany', resource, params);
- return Promise.resolve({
- data: params.ids.map(id => tags.find(tag => tag.id === id)),
- });
+const dataProvider = fakeRestProvider(
+ {
+ tags,
},
-});
+ process.env.NODE_ENV !== 'test',
+ process.env.NODE_ENV !== 'test' ? 300 : 0
+);
const i18nProvider = polyglotI18nProvider(() => englishMessages);
diff --git a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx
index 4049e3deca5..483c0bcfb16 100644
--- a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx
+++ b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx
@@ -100,15 +100,13 @@ export const ReferenceArrayInput = (props: ReferenceArrayInputProps) => {
const { isPaused, allChoices } = controllerProps;
return isPaused && allChoices == null ? (
- offline ?? (
-
-
-
- )
+
+ {offline ?? }
+
) : (
diff --git a/packages/ra-ui-materialui/src/input/ReferenceInput.stories.tsx b/packages/ra-ui-materialui/src/input/ReferenceInput.stories.tsx
index cc2c6472adb..30229ea8fe5 100644
--- a/packages/ra-ui-materialui/src/input/ReferenceInput.stories.tsx
+++ b/packages/ra-ui-materialui/src/input/ReferenceInput.stories.tsx
@@ -126,24 +126,42 @@ const LoadChildrenOnDemand = ({ children }: { children: React.ReactNode }) => {
);
};
-const BookEditOffline = () => (
- {
- console.log(data);
- },
- }}
- >
-
-
-
-
-
-
+export const Offline = ({ dataProvider = dataProviderWithAuthors }) => (
+
+
+
+ `${record.first_name} ${record.last_name}`
+ }
+ />
+ {
+ console.log(data);
+ },
+ }}
+ >
+
+
+
+
+
+
+ }
+ />
+
+
);
-export const Offline = ({ dataProvider = dataProviderWithAuthors }) => (
+export const CustomOffline = ({ dataProvider = dataProviderWithAuthors }) => (
(
`${record.first_name} ${record.last_name}`
}
/>
-
+ {
+ console.log(data);
+ },
+ }}
+ >
+
+
+ You're offline}
+ />
+
+
+
+ }
+ />
);
diff --git a/packages/ra-ui-materialui/src/input/ReferenceInput.tsx b/packages/ra-ui-materialui/src/input/ReferenceInput.tsx
index 73e00a4bc5c..384e18d8021 100644
--- a/packages/ra-ui-materialui/src/input/ReferenceInput.tsx
+++ b/packages/ra-ui-materialui/src/input/ReferenceInput.tsx
@@ -78,15 +78,13 @@ export const ReferenceInput = (props: ReferenceInputProps) => {
-
-
- )
+
+ {offline ?? }
+
}
>
{children}
From dbd8dd16b644360cdee8442e69728ed9d30908ad Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 17:05:26 +0200
Subject: [PATCH 53/58] Improve documentation
---
docs/DataProviders.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/DataProviders.md b/docs/DataProviders.md
index 848648dd569..2cfbb4b715a 100644
--- a/docs/DataProviders.md
+++ b/docs/DataProviders.md
@@ -905,10 +905,10 @@ import { addOfflineSupportToQueryClient } from 'react-admin';
import { QueryClient } from '@tanstack/react-query';
import { dataProvider } from './dataProvider';
-export const queryClient = new QueryClient();
+const baseQueryClient = new QueryClient();
-const queryClientWithOfflineSupport = addOfflineSupportToQueryClient({
- queryClient,
+export const queryClient = addOfflineSupportToQueryClient({
+ queryClient: baseQueryClient,
dataProvider,
resources: ['posts', 'comments'],
});
@@ -1007,14 +1007,14 @@ import { dataProvider } from './dataProvider';
const baseQueryClient = new QueryClient();
export const queryClient = addOfflineSupportToQueryClient({
- queryClient,
+ queryClient: baseQueryClient,
dataProvider,
resources: ['posts', 'comments'],
});
queryClient.setMutationDefaults('banUser', {
mutationFn: async (userId) => {
- return dataProviderFn.banUser(userId);
+ return dataProvider.banUser(userId);
},
});
```
\ No newline at end of file
From b8b13c52129e931a0eda140f655d3de601548984 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 17:09:56 +0200
Subject: [PATCH 54/58] Fix yarn.lock
---
yarn.lock | 45 ++++++++++++++++++++++++++-------------------
1 file changed, 26 insertions(+), 19 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index a0c44a43b5f..72cadabc668 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4355,6 +4355,13 @@ __metadata:
languageName: node
linkType: hard
+"@tanstack/query-core@npm:5.81.2":
+ version: 5.81.2
+ resolution: "@tanstack/query-core@npm:5.81.2"
+ checksum: 36a6bddec2e7512015bcfbb0d7b0876fab418de9e0ef21ad403598276960e0b7d53efd62832ce462738ad22d9883e31cb5403eafc65dfd9b2f6744c22a9d8e42
+ languageName: node
+ linkType: hard
+
"@tanstack/query-devtools@npm:5.47.0":
version: 5.47.0
resolution: "@tanstack/query-devtools@npm:5.47.0"
@@ -4362,22 +4369,22 @@ __metadata:
languageName: node
linkType: hard
-"@tanstack/query-persist-client-core@npm:5.47.0":
- version: 5.47.0
- resolution: "@tanstack/query-persist-client-core@npm:5.47.0"
+"@tanstack/query-persist-client-core@npm:5.81.2":
+ version: 5.81.2
+ resolution: "@tanstack/query-persist-client-core@npm:5.81.2"
dependencies:
- "@tanstack/query-core": "npm:5.47.0"
- checksum: b82e532bd618fd0f4b57eb653f88f0777d36db872184b5cff8033772778f838960ffbf46174cc902cb2260d7065dda4ce6f9b216e6b5d13f67cabd425a66a884
+ "@tanstack/query-core": "npm:5.81.2"
+ checksum: 65aebf52678cbadae81ec8bdf2764781cf975ed0eaf7ee37f77c536139da9f8c9220a1006c0ded50c1abd2d70ff0b76ed5e352dd4c6d3da782df240a2a3d3cbc
languageName: node
linkType: hard
-"@tanstack/query-sync-storage-persister@npm:5.47.0":
- version: 5.47.0
- resolution: "@tanstack/query-sync-storage-persister@npm:5.47.0"
+"@tanstack/query-sync-storage-persister@npm:^5.47.0":
+ version: 5.81.2
+ resolution: "@tanstack/query-sync-storage-persister@npm:5.81.2"
dependencies:
- "@tanstack/query-core": "npm:5.47.0"
- "@tanstack/query-persist-client-core": "npm:5.47.0"
- checksum: f82e1b68db259170711aaa5b76684d23131e9d272ffc78703583370823c21c06fedb8cd5e61f6df5228a369356b5527db8b6d9e467930374f942d1e70e34fea0
+ "@tanstack/query-core": "npm:5.81.2"
+ "@tanstack/query-persist-client-core": "npm:5.81.2"
+ checksum: cba3d0c0bf032c5a4aac5c49f2ed18f2660a2ae3640741a299a71097682cd15847a58c986827f0a01310d5da407591159cc1ed0b1a8b258100644a98aa554b8a
languageName: node
linkType: hard
@@ -4393,15 +4400,15 @@ __metadata:
languageName: node
linkType: hard
-"@tanstack/react-query-persist-client@npm:5.47.0":
- version: 5.47.0
- resolution: "@tanstack/react-query-persist-client@npm:5.47.0"
+"@tanstack/react-query-persist-client@npm:^5.47.0":
+ version: 5.81.2
+ resolution: "@tanstack/react-query-persist-client@npm:5.81.2"
dependencies:
- "@tanstack/query-persist-client-core": "npm:5.47.0"
+ "@tanstack/query-persist-client-core": "npm:5.81.2"
peerDependencies:
- "@tanstack/react-query": ^5.47.0
+ "@tanstack/react-query": ^5.81.2
react: ^18 || ^19
- checksum: 0bd0988f03811c5dcdc49c53f40a89495c1c11c9697eb15800d44c48ba626eb22fc421ca9348f84186406ec7652c10e36bde77790eddd14b934a38a90b488af4
+ checksum: cd4176744c6a96295d9c6441e212420b34f83f10f82c59de87aaba0d918de27964fb1d3cd0d91d2cff2e94624fe978a9eb33a348980bf35a624ccb8e5cec0a2f
languageName: node
linkType: hard
@@ -17908,10 +17915,10 @@ __metadata:
"@hookform/devtools": "npm:^4.3.3"
"@mui/icons-material": "npm:^5.16.12"
"@mui/material": "npm:^5.16.12"
- "@tanstack/query-sync-storage-persister": "npm:5.47.0"
+ "@tanstack/query-sync-storage-persister": "npm:^5.47.0"
"@tanstack/react-query": "npm:^5.21.7"
"@tanstack/react-query-devtools": "npm:^5.21.7"
- "@tanstack/react-query-persist-client": "npm:5.47.0"
+ "@tanstack/react-query-persist-client": "npm:^5.47.0"
"@vitejs/plugin-react": "npm:^4.3.4"
jsonexport: "npm:^3.2.0"
little-state-machine: "npm:^4.8.1"
From a3b1da8badadf7499cd169d831c83962f9266e67 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Thu, 26 Jun 2025 17:43:18 +0200
Subject: [PATCH 55/58] Document how to handle errors for resumed mutations
---
docs/DataProviders.md | 39 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/docs/DataProviders.md b/docs/DataProviders.md
index 2cfbb4b715a..6dbe6c8a1cf 100644
--- a/docs/DataProviders.md
+++ b/docs/DataProviders.md
@@ -1017,4 +1017,41 @@ queryClient.setMutationDefaults('banUser', {
return dataProvider.banUser(userId);
},
});
-```
\ No newline at end of file
+```
+
+## Handling Errors For Resumed Mutations
+
+If you enabled offline support, users might trigger mutations while being actually offline. When they're back online, react-query will _resume_ those mutations and they might fail for other reasons (server side validation or errors). However, as users might have navigated away from the page that triggered the mutation, they won't see any notification.
+
+To handle this scenario, you must register default `onError` side effects for all mutations (react-admin default ones or custom). If you want to leverage react-admin notifications, you can use a custom layout:
+
+```tsx
+// in src/Layout.tsx
+export const MyLayout = ({ children }: { children: React.ReactNode }) => {
+ const queryClient = useQueryClient();
+ const notify = useNotify();
+
+ React.useEffect(() => {
+ const mutationKeyFilter = []; // An empty array targets all mutations
+ queryClient.setMutationDefaults([], {
+ onSettled(data, error) {
+ if (error) {
+ notify(error.message, { type: 'error' });
+ }
+ },
+ });
+ }, [queryClient, notify]);
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+Note that this simple example will only show the error message as it was received. Users may not have the context to understand the error (what record or operation it relates to).
+Here are some ideas for a better user experience:
+
+- make sure your messages allow users to go to the pages related to the errors (you can leverage [custom notifications](./useNotify.md#custom-notification-content) for that)
+- store the notifications somewhere (server side or not) and show them in a custom page with proper links, etc.
From 4413807d61beaf34f5d47af73ac0d5f194d551e7 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Wed, 9 Jul 2025 17:09:16 +0200
Subject: [PATCH 56/58] Dedupe @tanstack/query-core
---
yarn.lock | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 72cadabc668..f6e8876baa0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4348,14 +4348,7 @@ __metadata:
languageName: node
linkType: hard
-"@tanstack/query-core@npm:5.47.0":
- version: 5.47.0
- resolution: "@tanstack/query-core@npm:5.47.0"
- checksum: 2d2378dbde2b0610b6356fcdb56904aa9d41c140c17ceb55e257b918c8555484ff36743a6a37768575631be96b9291eedc53723cd80326783095499cb97db049
- languageName: node
- linkType: hard
-
-"@tanstack/query-core@npm:5.81.2":
+"@tanstack/query-core@npm:5.47.0, @tanstack/query-core@npm:5.81.2":
version: 5.81.2
resolution: "@tanstack/query-core@npm:5.81.2"
checksum: 36a6bddec2e7512015bcfbb0d7b0876fab418de9e0ef21ad403598276960e0b7d53efd62832ce462738ad22d9883e31cb5403eafc65dfd9b2f6744c22a9d8e42
From 3fda18b7d6e3c89060de8ad0e7021e52015d2e8a Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Wed, 9 Jul 2025 17:10:59 +0200
Subject: [PATCH 57/58] Improve DataProviders documentation
---
docs/DataProviders.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/DataProviders.md b/docs/DataProviders.md
index 6dbe6c8a1cf..f6cacc58509 100644
--- a/docs/DataProviders.md
+++ b/docs/DataProviders.md
@@ -1021,7 +1021,7 @@ queryClient.setMutationDefaults('banUser', {
## Handling Errors For Resumed Mutations
-If you enabled offline support, users might trigger mutations while being actually offline. When they're back online, react-query will _resume_ those mutations and they might fail for other reasons (server side validation or errors). However, as users might have navigated away from the page that triggered the mutation, they won't see any notification.
+If you enabled offline support, users might trigger mutations while being actually offline. When they're back online, TanStack Query will _resume_ those mutations and they might fail for other reasons (server side validation or errors). However, as users might have navigated away from the page that triggered the mutation, they won't see any notification.
To handle this scenario, you must register default `onError` side effects for all mutations (react-admin default ones or custom). If you want to leverage react-admin notifications, you can use a custom layout:
@@ -1033,7 +1033,7 @@ export const MyLayout = ({ children }: { children: React.ReactNode }) => {
React.useEffect(() => {
const mutationKeyFilter = []; // An empty array targets all mutations
- queryClient.setMutationDefaults([], {
+ queryClient.setMutationDefaults(mutationKeyFilter, {
onSettled(data, error) {
if (error) {
notify(error.message, { type: 'error' });
From 66416c1e38f7a01d661c6de7c0ad738d6b93639d Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Wed, 9 Jul 2025 17:33:37 +0200
Subject: [PATCH 58/58] Fix following tanstack/query-core upgrade
---
packages/ra-core/src/auth/useAuthState.ts | 2 +-
packages/ra-core/src/auth/useCanAccess.ts | 2 +-
packages/ra-core/src/auth/usePermissions.ts | 5 ++++-
.../src/controller/input/useReferenceInputController.ts | 2 +-
.../src/dataProvider/addOfflineSupportToQueryClient.ts | 2 +-
packages/ra-core/src/dataProvider/useGetList.ts | 2 +-
packages/ra-core/src/dataProvider/useGetManyReference.ts | 2 +-
7 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/packages/ra-core/src/auth/useAuthState.ts b/packages/ra-core/src/auth/useAuthState.ts
index aa75ef8d5cd..868da334f94 100644
--- a/packages/ra-core/src/auth/useAuthState.ts
+++ b/packages/ra-core/src/auth/useAuthState.ts
@@ -150,7 +150,7 @@ const useAuthState = (
return authProvider != null
? result
- : (noAuthProviderQueryResult as UseAuthStateResult);
+ : (noAuthProviderQueryResult as unknown as UseAuthStateResult);
};
type UseAuthStateOptions = Omit<
diff --git a/packages/ra-core/src/auth/useCanAccess.ts b/packages/ra-core/src/auth/useCanAccess.ts
index 63db7906666..ead40b597dc 100644
--- a/packages/ra-core/src/auth/useCanAccess.ts
+++ b/packages/ra-core/src/auth/useCanAccess.ts
@@ -93,7 +93,7 @@ export const useCanAccess = <
return authProviderHasCanAccess
? result
- : (emptyQueryObserverResult as UseCanAccessResult);
+ : (emptyQueryObserverResult as unknown as UseCanAccessResult);
};
const emptyQueryObserverResult = {
diff --git a/packages/ra-core/src/auth/usePermissions.ts b/packages/ra-core/src/auth/usePermissions.ts
index eddf21142c1..9ef83079a08 100644
--- a/packages/ra-core/src/auth/usePermissions.ts
+++ b/packages/ra-core/src/auth/usePermissions.ts
@@ -108,7 +108,10 @@ const usePermissions = (
);
return !authProvider || !authProvider.getPermissions
- ? (fakeQueryResult as UsePermissionsResult)
+ ? (fakeQueryResult as unknown as UsePermissionsResult<
+ PermissionsType,
+ ErrorType
+ >)
: result;
};
diff --git a/packages/ra-core/src/controller/input/useReferenceInputController.ts b/packages/ra-core/src/controller/input/useReferenceInputController.ts
index e183c62c3bb..df5757c948b 100644
--- a/packages/ra-core/src/controller/input/useReferenceInputController.ts
+++ b/packages/ra-core/src/controller/input/useReferenceInputController.ts
@@ -118,8 +118,8 @@ export const useReferenceInputController = (
} = useReference({
id: currentValue,
reference,
- // @ts-ignore the types of the queryOptions for the getMAny and getList are not compatible
options: {
+ // @ts-ignore the types of the queryOptions for the getMany and getList are not compatible
enabled: currentValue != null && currentValue !== '',
meta,
...otherQueryOptions,
diff --git a/packages/ra-core/src/dataProvider/addOfflineSupportToQueryClient.ts b/packages/ra-core/src/dataProvider/addOfflineSupportToQueryClient.ts
index 3916a6a4037..360b17fa2bc 100644
--- a/packages/ra-core/src/dataProvider/addOfflineSupportToQueryClient.ts
+++ b/packages/ra-core/src/dataProvider/addOfflineSupportToQueryClient.ts
@@ -92,7 +92,7 @@ export const addOfflineSupportToQueryClient = ({
resources.forEach(resource => {
DataProviderMutations.forEach(mutation => {
queryClient.setMutationDefaults([resource, mutation], {
- mutationFn: async params => {
+ mutationFn: async (params: any) => {
const dataProviderFn = dataProvider[mutation] as Function;
return dataProviderFn.apply(dataProviderFn, ...params);
},
diff --git a/packages/ra-core/src/dataProvider/useGetList.ts b/packages/ra-core/src/dataProvider/useGetList.ts
index aa24430d175..e16296dfb3a 100644
--- a/packages/ra-core/src/dataProvider/useGetList.ts
+++ b/packages/ra-core/src/dataProvider/useGetList.ts
@@ -176,7 +176,7 @@ export const useGetList = <
}
: result,
[result]
- ) as UseQueryResult & {
+ ) as unknown as UseQueryResult & {
total?: number;
pageInfo?: {
hasNextPage?: boolean;
diff --git a/packages/ra-core/src/dataProvider/useGetManyReference.ts b/packages/ra-core/src/dataProvider/useGetManyReference.ts
index 001e930fc6b..c6639a9147d 100644
--- a/packages/ra-core/src/dataProvider/useGetManyReference.ts
+++ b/packages/ra-core/src/dataProvider/useGetManyReference.ts
@@ -155,7 +155,7 @@ export const useGetManyReference = <
}
: result,
[result]
- ) as UseQueryResult & {
+ ) as unknown as UseQueryResult & {
total?: number;
pageInfo?: {
hasNextPage?: boolean;