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

[BUG] Problems with useForm inside Custom Page (antd UI framework) #1729

Closed
eliasfeijo opened this issue Apr 18, 2022 · 4 comments
Closed

[BUG] Problems with useForm inside Custom Page (antd UI framework) #1729

eliasfeijo opened this issue Apr 18, 2022 · 4 comments
Labels
bug Something isn't working

Comments

@eliasfeijo
Copy link

I'm trying to make an Edit page for a resource, using a Custom Page. But I'm having some problems with the useForm hook, using the antd Form component.

  1. The useForm hook doesn't automatically fetch the initial data, to populate the Form. I had to manually fetch it, using the hook useOne, and fill the data inside the form. I'm already setting the resource parameter inside the useForm hook and Edit component (with recordItemId aswell), but it only works for the form submit.
  2. The <Input> fields inside the <Form.Item> fields don't update the value prop that I've set, using a variable with useState. But it works fine if I put the <Input> field outside the Form component. I've fixed it using the form return value of the hook useForm, and manually calling the function form.setFieldsValue({ name }); when the field value changes.
  3. The <RefreshButton> uses the URL from the page to refetch the resource, instead of using the provided resource parameter of the useForm hook. I had to manually set the RefreshButton inside the Form (using pageHeaderProps), and use the refetch return value of the hook useOne, and set the data manually for it to work.

Here is the code that I've implemented to make it work:

App.tsx:

<Refine
        routerProvider={{
          ...routerProvider,
          routes: [
            { element: <EmployeeList />, path: "/employees", layout: true },
            {
              element: <EmployeeCreate />,
              path: "/employees/create",
              layout: true,
            },
            {
              element: <EmployeeEdit />,
              path: "/employees/edit/:id",
              layout: true,
            },
          ],
        }}
        ...
/>

pages/employees/edit.tsx (Custom Page):

import {
  IResourceComponentsProps,
  useNavigation,
  useOne,
  useTranslate,
} from "@pankod/refine-core";
import { Edit, Form, Input, RefreshButton, useForm } from "@pankod/refine-antd";
import { useParams } from "react-router-dom";

import { IEmployee } from "interfaces";
import { useContext, useEffect, useState } from "react";
import { AppContext } from "contexts/AppContext";

export const EmployeeEdit: React.FC<IResourceComponentsProps> = () => {
  const { state } = useContext(AppContext);
  const { id } = useParams();
  const t = useTranslate();
  const { push } = useNavigation();

  const [loadedInitialData, setLoadedInitialData] = useState(false);
  const [name, setName] = useState<string>("");

  const { data, refetch } = useOne<IEmployee>({
    resource: `stores/${state.storeId}/employees`,
    id: id as string,
  });

  const { form, formProps, saveButtonProps, onFinish } = useForm<IEmployee>({
    action: "edit",
    resource: `stores/${state.storeId}/employees/${id}`,
  });

  useEffect(() => {
    if (!loadedInitialData && data) {
      setLoadedInitialData(true);
      setName(data.data.name);
    }
  }, [data, loadedInitialData]);

  useEffect(() => {
    form.setFieldsValue({ name });
  }, [form, name]);

  const onSubmit = async () => {
    await onFinish?.();
    push("/employees");
  };

  return (
    <Edit
      saveButtonProps={saveButtonProps}
      resource={`stores/${state.storeId}/employees`}
      title={t("employees.titles.edit")}
      recordItemId={id}
      pageHeaderProps={{
        extra: [
          <RefreshButton
            resourceName={`stores/${state.storeId}/employees`}
            recordItemId={id}
            onClick={() => {
              setLoadedInitialData(false);
              refetch();
            }}
          />,
        ],
      }}
    >
      <Form form={form} {...formProps} layout="vertical" onFinish={onSubmit}>
        <Form.Item
          label={t("employees.fields.name")}
          name="name"
          rules={[
            {
              required: true,
            },
          ]}
        >
          <Input
            value={name}
            onChange={(e) => {
              setName(e.target.value);
            }}
          />
        </Form.Item>
      </Form>
    </Edit>
  );
};

I was expecting that the useForm hook behaved the same as resources for Custom Pages, so that I wouldn't have to implement all this logic by myself, so I'm opening this issue as a bug.

@eliasfeijo eliasfeijo added the bug Something isn't working label Apr 18, 2022
@omeraplak
Copy link
Member

omeraplak commented Apr 18, 2022

Hey @eliasfeijo 👋,
In fact, all you have to do is call the "setId" function, which returns from the useForm hook, with the id of the record you want to edit. So you won't need the useOne hook.

You can use refetch function of queryResult of useForm for "RefreshButton".

For more details; https://refine.dev/docs/core/hooks/useForm/#action-edit

@eliasfeijo
Copy link
Author

Hello @omeraplak, thank you for the quick response! Previously, I didn't see this section on the documentation (about the "edit" action and the setId function). I've updated my code with your suggestion, and now it's working like a charm!

Fixed code:

pages/employees/edit.tsx:

import {
  IResourceComponentsProps,
  useNavigation,
  useTranslate,
} from "@pankod/refine-core";
import { Edit, Form, Input, RefreshButton, useForm } from "@pankod/refine-antd";
import { useParams } from "react-router-dom";

import { IEmployee } from "interfaces";
import { useContext, useEffect } from "react";
import { AppContext } from "contexts/AppContext";

export const EmployeeEdit: React.FC<IResourceComponentsProps> = () => {
  const { state } = useContext(AppContext);
  const { id } = useParams();
  const t = useTranslate();
  const { push } = useNavigation();

  const {
    form,
    setId,
    formProps,
    saveButtonProps,
    queryResult,
    onFinish,
    formLoading,
  } = useForm<IEmployee>({
    action: "edit",
    resource: `stores/${state.storeId}/employees`,
  });

  useEffect(() => {
    setId(id);
  }, [setId, id]);

  const onSubmit = async () => {
    await onFinish?.();
    push("/employees");
  };

  return (
    <Edit
      isLoading={formLoading}
      saveButtonProps={saveButtonProps}
      resource={`stores/${state.storeId}/employees`}
      title={t("employees.titles.edit")}
      recordItemId={id}
      pageHeaderProps={{
        extra: [
          <RefreshButton
            resourceName={`stores/${state.storeId}/employees`}
            recordItemId={id}
            onClick={() => {
              queryResult?.refetch();
            }}
          />,
        ],
      }}
    >
      <Form form={form} {...formProps} layout="vertical" onFinish={onSubmit}>
        <Form.Item
          label={t("employees.fields.name")}
          name="name"
          rules={[
            {
              required: true,
            },
          ]}
        >
          <Input />
        </Form.Item>
      </Form>
    </Edit>
  );
};

@eliasfeijo
Copy link
Author

eliasfeijo commented Apr 18, 2022

Now that I'm thinking about it, it would be even better if we could just set the id inside the resource parameter of useForm. Then we wouldn't have to call the setId function manually, and the form would automatically load the initial data. And the <RefreshButton> could refer to the resource parameter for the fetch, and we wouldn't have to call the queryResult.refetch() manually.

Is it possible, @omeraplak? It's totally not a critical feature, but it would be nice to have in a future version of refine. :)

@omeraplak
Copy link
Member

omeraplak commented Apr 19, 2022

Hey @eliasfeijo ,
Yes, you are right. We have id support in useShow hook. It can also be added for useForm. Can you create an issue for us?

Actually, you don't need to override RefreshButton's onClick function. It should work this way.

  <RefreshButton
       resourceName={`stores/${state.storeId}/employees`}
       recordItemId={id}
 />,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants