refine icon indicating copy to clipboard operation
refine copied to clipboard

[BUG] useForm from Refine React hook form package is not detecting current route

Open Cavdy opened this issue 1 year ago • 4 comments

Describe the bug

useForm from @refinedev/react-hook-form is not detecting current route, I have to manually add the resource route for it to work

Steps To Reproduce

  1. create a new refine nextjs app with npm create refine-app@latest -- -o refine-nextjs my-refine-nextjs-app
  2. create a new resource { name: "workspaces", list: "/workspaces", create: "/workspaces/create", meta: { canDelete: true, }, },
  3. create a new page in pages workspaces/create/index.tsx
  4. create few input fields and validate using zod and @refinedev/react-hook-form (I think you can skip the zod part)
  5. try saving by pressing the submit button, it currently use the resource name instead of the create resource

Expected behavior

From the documentation, I expect it to detect the current route URL and use the appropriate resource without having to manually include it

Screenshot

Screenshot 2024-02-05 at 12 04 20 (2) Screenshot 2024-02-05 at 12 05 59 (2)

Desktop

  • OS: [macbook]
  • Browser [chrome]
  • Version [121.0.6167.139]
  • Used dataProvider [@refinedev/simple-rest]

Mobile

No response

Additional Context

No response

Cavdy avatar Feb 05 '24 11:02 Cavdy

Hey @Cavdy thanks for the report! We'll check it and get back to you.

BatuhanW avatar Feb 05 '24 17:02 BatuhanW

Hello @Cavdy could you give us a minimal reproducible example? While the report details are good, we can't easily understand the problem without the code.

BatuhanW avatar Feb 06 '24 09:02 BatuhanW

Hello @Cavdy,

After @BatuhanW, I tried too to reproduce this issue but I couldn't. Everything works as expected.

Can you provide a reproducible example, please?

Tested with the following code:

From http://localhost:3000/workspaces/create URL, useForm sends this request.

Request URL:
https://api.fake-rest.refine.dev/workspaces
Request Method: POST
  • pages/_app.tsx
import { GitHubBanner, Refine } from '@refinedev/core'
import { DevtoolsPanel, DevtoolsProvider } from '@refinedev/devtools'
import { RefineKbar, RefineKbarProvider } from '@refinedev/kbar'
import { RefineSnackbarProvider, ThemedLayoutV2, notificationProvider } from '@refinedev/mui'
import routerProvider, { DocumentTitleHandler, UnsavedChangesNotifier } from '@refinedev/nextjs-router'
import type { NextPage } from 'next'
import { AppProps } from 'next/app'

import { Header } from '@components/header'
import { ColorModeContextProvider } from '@contexts'
import CssBaseline from '@mui/material/CssBaseline'
import GlobalStyles from '@mui/material/GlobalStyles'
import dataProvider from '@refinedev/simple-rest'
import { appWithTranslation, useTranslation } from 'next-i18next'
import { authProvider } from 'src/authProvider'

const API_URL = 'https://api.fake-rest.refine.dev'

export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  noLayout?: boolean
}

type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}

function MyApp({ Component, pageProps }: AppPropsWithLayout): JSX.Element {
  const renderComponent = () => {
    if (Component.noLayout) {
      return <Component {...pageProps} />
    }

    return (
      <ThemedLayoutV2 Header={() => <Header sticky />}>
        <Component {...pageProps} />
      </ThemedLayoutV2>
    )
  }

  const { t, i18n } = useTranslation()

  const i18nProvider = {
    translate: (key: string, params: object) => t(key, params),
    changeLocale: (lang: string) => i18n.changeLanguage(lang),
    getLocale: () => i18n.language,
  }

  return (
    <>
      <GitHubBanner />
      <RefineKbarProvider>
        <ColorModeContextProvider>
          <CssBaseline />
          <GlobalStyles styles={{ html: { WebkitFontSmoothing: 'auto' } }} />
          <RefineSnackbarProvider>
            <DevtoolsProvider>
              <Refine
                routerProvider={routerProvider}
                dataProvider={dataProvider(API_URL)}
                notificationProvider={notificationProvider}
                authProvider={authProvider}
                i18nProvider={i18nProvider}
                resources={[
                  {
                    name: 'workspaces',
                    list: '/workspaces',
                    create: '/workspaces/create',
                    edit: '/workspaces/edit/:id',
                    show: '/workspaces/show/:id',
                    meta: {
                      canDelete: true,
                    },
                  },
                ]}
                options={{
                  syncWithLocation: true,
                  warnWhenUnsavedChanges: true,
                  useNewQueryKeys: true,
                }}>
                {renderComponent()}
                <RefineKbar />
                <UnsavedChangesNotifier />
                <DocumentTitleHandler />
              </Refine>
              <DevtoolsPanel />
            </DevtoolsProvider>
          </RefineSnackbarProvider>
        </ColorModeContextProvider>
      </RefineKbarProvider>
    </>
  )
}

export default appWithTranslation(MyApp)
  • pages/workspaces/create/index.tsx
import { Create } from '@refinedev/mui'
import { Box, TextField } from '@mui/material'
import { useForm } from '@refinedev/react-hook-form'
import { IResourceComponentsProps, useTranslate } from '@refinedev/core'

const WorkspaceCreate: React.FC<IResourceComponentsProps> = () => {
  const translate = useTranslate()
  const {
    saveButtonProps,
    refineCore: { formLoading },
    register,
    formState: { errors },
  } = useForm()

  return (
    <Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
      <Box component='form' sx={{ display: 'flex', flexDirection: 'column' }} autoComplete='off'></Box>
      <TextField
        {...register('title', {
          required: 'This field is required',
        })}
        error={!!(errors as any)?.title}
        helperText={(errors as any)?.title?.message}
        margin='normal'
        fullWidth
        InputLabelProps={{ shrink: true }}
        type='text'
        label={translate('posts.fields.title')}
        name='title'
      />
    </Create>
  )
}

export default WorkspaceCreate

alicanerdurmaz avatar Feb 06 '24 09:02 alicanerdurmaz

Hey @BatuhanW @alicanerdurmaz

Here is repo I created with the reproducible issue... you can clone it and go to http://localhost:3002/workspaces/create

Repo https://github.com/cavdy-play/refine-minimal

Cavdy avatar Feb 06 '24 10:02 Cavdy

Hey @Cavdy,

Let's change API_URL to 'http://localhost:3002/api'. After the change, you can see your request in the network tab. We can't see requests for localhost:3000 because there is no server on that port.

Everything works as expected. What do you expect?

image image

alicanerdurmaz avatar Feb 07 '24 07:02 alicanerdurmaz

Hey @alicanerdurmaz

The resource endpoint specified is workspaces/create but it's using workspaces as you can see from the screenshot you shared. I thought it's suppose to use the appropriate action url which is workspaces/create

Cavdy avatar Feb 07 '24 08:02 Cavdy

@Cavdy Oh, I see. This is expected. converting resource to url is the responsibility of the data provider. You can see how simple-rest generates url here.

I guess you are confused when you give create: "/workspaces/create" to resource and I think you are not wrong, but this definition is for page route, not for api url.

you have 2 options to solve this issue:

  • you can create your data provider with swizzle.
  • you can give resource: "/workspaces/create to your data hook.

alicanerdurmaz avatar Feb 07 '24 08:02 alicanerdurmaz

Thanks @alicanerdurmaz

Cavdy avatar Feb 07 '24 08:02 Cavdy

Is there a way to pass a custom resource (e.g. in a mutate call) and specify where the id should be interpolated? For example, I want to do

mutate({
  resource: "/products/:id/customize",
  values: {
    name: "New Product",
    material: "Wood",
  },
  id: 1,
});

But that will interpret the string literally and pass id to the end of the route. Right now the only work around I've found is to pass an interpolated string and empty id

evansendra avatar Jun 25 '24 16:06 evansendra