[BUG] Resource routes using path param other than id not being substituted in URL
Closed this issue · 1 comments
Describe the bug
We would like to route based on a unique slug using react router and the refine resource definitions rather than the ids to have more readable URLs. When we try and use a resource path such as show: "/publishers/show/:handle"
it doesn't substitute the handle field but rather puts :handle
directly in the URL.
Do Refine resources only accept the :id
path param?
Example App.tsx after bootstrap
import { Authenticated, GitHubBanner, Refine } from "@refinedev/core";
import { DevtoolsPanel, DevtoolsProvider } from "@refinedev/devtools";
import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar";
import {
AuthPage,
ErrorComponent,
ThemedLayoutV2,
ThemedSiderV2,
useNotificationProvider,
} from "@refinedev/antd";
import "@refinedev/antd/dist/reset.css";
import routerBindings, {
CatchAllNavigate,
DocumentTitleHandler,
NavigateToResource,
UnsavedChangesNotifier,
} from "@refinedev/react-router-v6";
import { dataProvider, liveProvider } from "@refinedev/supabase";
import { App as AntdApp } from "antd";
import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom";
import authProvider from "./authProvider";
import { Header } from "./components/header";
import { ColorModeContextProvider } from "./contexts/color-mode";
import { supabaseClient } from "./utility";
import { PublisherCreate, PublishersEdit, PublisherList, PublisherShow } from "./pages/publishers";
function App() {
return (
(<BrowserRouter>
<GitHubBanner />
<RefineKbarProvider>
<ColorModeContextProvider>
<AntdApp>
<DevtoolsProvider>
<Refine
dataProvider={dataProvider(supabaseClient)}
liveProvider={liveProvider(supabaseClient)}
authProvider={authProvider}
routerProvider={routerBindings}
notificationProvider={useNotificationProvider}
resources={[{
name: "publisher",
list: "/publishers",
show: "/publishers/show/:handle",
meta: {
label: 'Publishers',
}
}]}
options={{
syncWithLocation: true,
warnWhenUnsavedChanges: true,
useNewQueryKeys: true,
projectId: "******",
}}
>
<Routes>
<Route
element={
<Authenticated
key="authenticated-inner"
fallback={<CatchAllNavigate to="/login" />}
>
<ThemedLayoutV2
Header={Header}
Sider={(props) => <ThemedSiderV2 {...props} fixed />}
>
<Outlet />
</ThemedLayoutV2>
</Authenticated>
}
>
<Route
index
element={<NavigateToResource resource="publisher" />}
/>
<Route path="/publishers">
<Route index element={<PublisherList />} />
<Route path="show/:handle" element={<PublisherShow />} />
</Route>
<Route path="*" element={<ErrorComponent />} />
</Route>
<Route
element={
<Authenticated
key="authenticated-outer"
fallback={<Outlet />}
>
<NavigateToResource />
</Authenticated>
}
>
<Route
path="/login"
element={
<AuthPage
type="login"
formProps={{
initialValues: {
email: "info@refine.dev",
password: "refine-supabase",
},
}}
/>
}
/>
<Route
path="/register"
element={<AuthPage type="register" />}
/>
<Route
path="/forgot-password"
element={<AuthPage type="forgotPassword" />}
/>
</Route>
</Routes>
<RefineKbar />
<UnsavedChangesNotifier />
<DocumentTitleHandler />
</Refine>
<DevtoolsPanel />
</DevtoolsProvider>
</AntdApp>
</ColorModeContextProvider>
</RefineKbarProvider>
</BrowserRouter>)
);
}
export default App;
Example pages/publishers/list.tsx
import { useTable, List, EditButton, ShowButton } from "@refinedev/antd";
import { Table, Space } from "antd";
import { Publisher } from "../../types/publisher";
export const PublisherList = () => {
const { tableProps } = useTable<Publisher>({ });
return (
<List>
<Table {...tableProps} rowKey="handle">
<Table.Column
dataIndex="id"
title="Id"
/>
<Table.Column
dataIndex="publisher_name"
title="Publisher Name"
/>
<Table.Column
dataIndex="handle"
title="Handle"
/>
<Table.Column
title="Actions"
dataIndex="actions"
render={(_, record: Publisher) => (
<Space>
<EditButton
hideText
size="small"
recordItemId={record.handle}
/>
<ShowButton
hideText
size="small"
recordItemId={record.handle}
/>
</Space>
)}
/>
</Table>
</List>
);
};
Example pages/publishers/show.tsx
import { Show, TextField } from "@refinedev/antd";
import { useShow } from "@refinedev/core";
import { Typography } from "antd";
import { Publisher } from "../../types/publisher";
const { Title } = Typography;
export const PublisherShow = () => {
const { queryResult } = useShow<Publisher>({ });
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Title level={5}>{"ID"}</Title>
<TextField value={record?.id} />
<Title level={5}>{"Publisher Name"}</Title>
<TextField value={record?.publisher_name} />
<Title level={5}>{"Handle"}</Title>
<TextField value={record?.handle} />
</Show>
);
};
Steps To Reproduce
- Use Refine CLI to bootstrap project with Vite, React Router, Supabase, and AntDesign
- Copy example App.tsx and publisher pages from bug description
- Replace projectId with your projectId
- Configure supabase client (an unlinked local project will work)
- Add
publisher
table with columns:id
,publisher_name
,handle
into supabase - Add row with
publisher_name: "Test Publisher"
andhandle: "test-publisher"
- Start refine app and open in browser
- Find
Test Publisher
in table and click the view icon - Note the url includes the raw string
:handle
Expected behavior
Refine correctly substitutes the :handle
param with the handle
field from the entity in the URL.
Packages
- @refinedev/antd
- @refinedev/core
- @refinedev/react-router-v6
Additional Context
No response
Found it. It looks like this is because ShowButton
actually functions under the assumption that you're using the id
param and only substitutes that. Quick fix for anyone interested would be to swizzle out that component or just use the onClick
param, that seems to override the behavior. So something like:
<ShowButton
hideText
size="small"
onClick={() => go({ to: `show/${record.handle}` })}
/>