Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TabbedForm and TabbedShowLayout with react-router v7 #10469

Merged
merged 4 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/ra-core/src/routing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './useScrollToTop';
export * from './useRestoreScrollPosition';
export * from './types';
export * from './TestMemoryRouter';
export * from './useSplatPathBase';
24 changes: 24 additions & 0 deletions packages/ra-core/src/routing/useSplatPathBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useLocation, useParams } from 'react-router-dom';

/**
* Utility hook to get the base path of a splat path.
* Compatible both with react-router v6 and v7.
*
* Example:
* If a splat path is defined as `/posts/:id/show/*`,
* and the current location is `/posts/12/show/3`,
* this hook will return `/posts/12/show`.
*
* Solution inspired by
* https://github.com/remix-run/react-router/issues/11052#issuecomment-1828470203
*/
export const useSplatPathBase = () => {
const location = useLocation();
const params = useParams();
const splatPathRelativePart = params['*'];
const splatPathBase = location.pathname.replace(
new RegExp(`/${splatPathRelativePart}$`),
''
);
return splatPathBase;
};
9 changes: 7 additions & 2 deletions packages/ra-ui-materialui/src/detail/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
styled,
} from '@mui/material';
import { ResponsiveStyleValue } from '@mui/system';
import { useTranslate, RaRecord } from 'ra-core';
import { useTranslate, RaRecord, useSplatPathBase } from 'ra-core';
import clsx from 'clsx';

import { Labeled } from '../Labeled';
Expand Down Expand Up @@ -76,9 +76,14 @@ export const Tab = ({
}: TabProps) => {
const translate = useTranslate();
const location = useLocation();
const splatPathBase = useSplatPathBase();
const newPathName =
value == null || value === ''
? splatPathBase
: `${splatPathBase}/${value}`;
const propsForLink = {
component: Link,
to: { ...location, pathname: value },
to: { ...location, pathname: newPathName },
};

const renderHeader = () => {
Expand Down
12 changes: 8 additions & 4 deletions packages/ra-ui-materialui/src/form/FormTabHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ReactElement, ReactNode } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Tab as MuiTab, TabProps as MuiTabProps } from '@mui/material';
import clsx from 'clsx';
import { useTranslate, useFormGroup } from 'ra-core';
import { useTranslate, useFormGroup, useSplatPathBase } from 'ra-core';

import { TabbedFormClasses } from './TabbedFormView';

Expand All @@ -18,12 +18,16 @@ export const FormTabHeader = ({
...rest
}: FormTabHeaderProps): ReactElement => {
const translate = useTranslate();
const location = useLocation();
const formGroup = useFormGroup(value.toString());

const location = useLocation();
const splatPathBase = useSplatPathBase();
const newPathName =
value == null || value === ''
? splatPathBase
: `${splatPathBase}/${value}`;
const propsForLink = {
component: Link,
to: { ...location, pathname: value },
to: { ...location, pathname: newPathName },
};

let tabLabel =
Expand Down
14 changes: 4 additions & 10 deletions packages/ra-ui-materialui/src/form/TabbedFormView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,10 @@ import {
useState,
} from 'react';
import clsx from 'clsx';
import {
Routes,
Route,
matchPath,
useResolvedPath,
useLocation,
} from 'react-router-dom';
import { Routes, Route, matchPath, useLocation } from 'react-router-dom';
import { CardContent, Divider, SxProps } from '@mui/material';
import { styled } from '@mui/material/styles';
import { useResourceContext } from 'ra-core';
import { useResourceContext, useSplatPathBase } from 'ra-core';
import { Toolbar } from './Toolbar';
import { TabbedFormTabs, getTabbedFormTabFullPath } from './TabbedFormTabs';

Expand All @@ -35,9 +29,9 @@ export const TabbedFormView = (props: TabbedFormViewProps): ReactElement => {
...rest
} = props;
const location = useLocation();
const resolvedPath = useResolvedPath('');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

useResolvedPath can no longer be used with both v6 and v7. See https://reactrouter.com/en/6.28.2/hooks/use-resolved-path

const resource = useResourceContext(props);
const [tabValue, setTabValue] = useState(0);
const splatPathBase = useSplatPathBase();

const handleTabChange = (event: ChangeEvent<{}>, value: any): void => {
if (!syncWithLocation) {
Expand Down Expand Up @@ -82,7 +76,7 @@ export const TabbedFormView = (props: TabbedFormViewProps): ReactElement => {
const tabPath = getTabbedFormTabFullPath(tab, index);
const hidden = syncWithLocation
? !matchPath(
`${resolvedPath.pathname}/${tabPath}`,
`${splatPathBase}/${tabPath}`,
// The current location might have encoded segments (e.g. the record id) but resolvedPath.pathname doesn't
// and the match would fail.
getDecodedPathname(location.pathname)
Expand Down
Loading