stormy-gold
stormy-gold2d ago

Modify data after fetching and before submitting with `useForm`

I have two types FormType and ApiType. My form uses FormType, while my data provider uses ApiType. How do I handle this using useForm? Specifically, my data provider returns objects of ApiType. However, my form does not follow this type, but instead requires a mutation apiToForm: (data: ApiType) => FormType) to transform from ApiType to FormType. On submitting, the transformation formToApi: (formData: FormType) => ApiType should be done. I am using AntDesign, but am open to a solution using any design system.
11 Replies
kapa.ai
kapa.ai2d ago
Hi @Hugo Kerstens. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To handle the scenario where your form uses FormType and your data provider uses ApiType with useForm, you can use the approach of modifying data before submission. This can be done using the Ant Design implementation of useForm. Here's how you can achieve this: 1. For transforming the API data to form data when fetching: You can use the transformValues option of useForm to transform the data received from the API into the format your form expects. 2. For transforming form data to API data when submitting: You can create a custom onFinish handler to transform the form data before sending it to the API. Here's an example of how you can implement this:
import { useForm, Create } from "@refinedev/antd";
import { Form, Input } from "antd";

const CreatePage = () => {
const { formProps, saveButtonProps, onFinish } = useForm({
// Transform API data to form data
transformValues: (values) => ({
name: values.fullName.split(' ')[0],
surname: values.fullName.split(' ')[1],
}),
});

// Custom onFinish handler to transform form data to API data
const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
onFinish(apiData);
};

return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} onFinish={handleOnFinish} layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Surname" name="surname">
<Input />
</Form.Item>
</Form>
</Create>
);
};
import { useForm, Create } from "@refinedev/antd";
import { Form, Input } from "antd";

const CreatePage = () => {
const { formProps, saveButtonProps, onFinish } = useForm({
// Transform API data to form data
transformValues: (values) => ({
name: values.fullName.split(' ')[0],
surname: values.fullName.split(' ')[1],
}),
});

// Custom onFinish handler to transform form data to API data
const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
onFinish(apiData);
};

return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} onFinish={handleOnFinish} layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Surname" name="surname">
<Input />
</Form.Item>
</Form>
</Create>
);
};
In this example: 1. The transformValues option in useForm is used to transform the API data (ApiType) to form data (FormType) when the form is initially populated. 2. The custom handleOnFinish function is used to transform the form data (FormType) back to the API data format (ApiType) before submission. This approach allows you to work with different types for your form and API while still using the useForm hook effectively. For more details on modifying data before submission with Ant Design, you can refer to the Refine documentation on modifying data before submission. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2d ago
<@242226786145009665> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
stormy-gold
stormy-goldOP2d ago
@kapa.ai I am sorry, but transformValues only exists for Mantine UI. On top of that, it does the opposite of what you claim that it does.
kapa.ai
kapa.ai2d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I apologize for the confusion in my previous response. You are correct, and I thank you for pointing out the error. Let me provide a more accurate answer based on the available information for Ant Design. You're right that transformValues is not available for Ant Design's useForm hook, and I made a mistake in suggesting its use. Let's correct this and provide a solution that works with Ant Design. For Ant Design, we can modify the data before submission using a custom onFinish handler. Here's how you can handle the transformation between ApiType and FormType: 1. For transforming API data to form data when fetching: You can use the form.setFieldsValue() method within a useEffect hook after the data is fetched. 2. For transforming form data to API data when submitting: You can create a custom onFinish handler to transform the form data before sending it to the API. Here's an example of how you can implement this with Ant Design:
import { useForm, Create } from "@refinedev/antd";
import { Form, Input } from "antd";
import { useEffect } from "react";

const CreatePage = () => {
const { formProps, saveButtonProps, onFinish, queryResult } = useForm();

// Transform API data to form data when data is fetched
useEffect(() => {
const apiData = queryResult?.data?.data;
if (apiData) {
const formData = {
name: apiData.fullName.split(' ')[0],
surname: apiData.fullName.split(' ')[1],
};
formProps.form?.setFieldsValue(formData);
}
}, [queryResult?.data]);

// Custom onFinish handler to transform form data to API data
const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
onFinish(apiData);
};
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} onFinish={handleOnFinish} layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Surname" name="surname">
<Input />
</Form.Item>
</Form>
</Create>
);
};
import { useForm, Create } from "@refinedev/antd";
import { Form, Input } from "antd";
import { useEffect } from "react";

const CreatePage = () => {
const { formProps, saveButtonProps, onFinish, queryResult } = useForm();

// Transform API data to form data when data is fetched
useEffect(() => {
const apiData = queryResult?.data?.data;
if (apiData) {
const formData = {
name: apiData.fullName.split(' ')[0],
surname: apiData.fullName.split(' ')[1],
};
formProps.form?.setFieldsValue(formData);
}
}, [queryResult?.data]);

// Custom onFinish handler to transform form data to API data
const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
onFinish(apiData);
};
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} onFinish={handleOnFinish} layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Surname" name="surname">
<Input />
</Form.Item>
</Form>
</Create>
);
};
This approach allows you to work with different types for your form and API while using the Ant Design useForm hook. For more details on modifying data before submission with Ant Design, you can refer to the Refine documentation on changing form data before submitting to the API. Again, I apologize for the earlier confusion, and thank you for bringing this to my attention. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2d ago
<@242226786145009665> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
stormy-gold
stormy-goldOP2d ago
@kapa.ai Thanks, can you add the type parameters to useForm?
kapa.ai
kapa.ai2d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Certainly! I'd be happy to add the type parameters to useForm for you. Based on the information provided in the knowledge sources, we can specify the type parameters for useForm to separate the types for form structure and DataProvider submission. Here's how you can modify the code to include the type parameters:
import { useForm, Create } from "@refinedev/antd";
import { Form, Input } from "antd";
import { useEffect } from "react";
import { HttpError } from "@refinedev/core";

interface ApiType {
fullName: string;
}

interface FormType {
name: string;
surname: string;
}

const CreatePage = () => {
const { formProps, saveButtonProps, onFinish, queryResult } = useForm<
ApiType,
HttpError,
FormType
>();

// Transform API data to form data when data is fetched
useEffect(() => {
const apiData = queryResult?.data?.data;
if (apiData) {
const formData: FormType = {
name: apiData.fullName.split(' ')[0],
surname: apiData.fullName.split(' ')[1],
};
formProps.form?.setFieldsValue(formData);
}
}, [queryResult?.data]);

// Custom onFinish handler to transform form data to API data
const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
onFinish(apiData);
};

return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} onFinish={handleOnFinish} layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Surname" name="surname">
<Input />
</Form.Item>
</Form>
</Create>
);
};
import { useForm, Create } from "@refinedev/antd";
import { Form, Input } from "antd";
import { useEffect } from "react";
import { HttpError } from "@refinedev/core";

interface ApiType {
fullName: string;
}

interface FormType {
name: string;
surname: string;
}

const CreatePage = () => {
const { formProps, saveButtonProps, onFinish, queryResult } = useForm<
ApiType,
HttpError,
FormType
>();

// Transform API data to form data when data is fetched
useEffect(() => {
const apiData = queryResult?.data?.data;
if (apiData) {
const formData: FormType = {
name: apiData.fullName.split(' ')[0],
surname: apiData.fullName.split(' ')[1],
};
formProps.form?.setFieldsValue(formData);
}
}, [queryResult?.data]);

// Custom onFinish handler to transform form data to API data
const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
onFinish(apiData);
};

return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} onFinish={handleOnFinish} layout="vertical">
<Form.Item label="Name" name="name">
<Input />
</Form.Item>
<Form.Item label="Surname" name="surname">
<Input />
</Form.Item>
</Form>
</Create>
);
};
In this example, we've added the type parameters to useForm: 1. ApiType: This is the type of data that the API expects and returns. 2. HttpError: This is the default error type used by Refine. 3. FormType: This is the type of data used in the form. By specifying these type parameters, we ensure type safety throughout the form handling process. The queryResult will contain data of type ApiType, while the form fields and handleOnFinish function work with FormType. This approach aligns with the information provided in the Ant Design useForm documentation, which mentions that useForm accepts type parameters for the record in use and for the response type of the mutation. Remember that this is a basic example, and you might need to adjust it based on your specific use case and any additional type parameters required by your implementation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2d ago
<@242226786145009665> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
stormy-gold
stormy-goldOP2d ago
@kapa.ai Unfortunately, onFinish expects a value of the type FormType, namely the third type parameter of useForm, so this does not work with type safety. The examples in the documentation glance over this fact.
kapa.ai
kapa.ai2d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- You're absolutely correct, and I apologize for overlooking this important detail. You've identified a key issue that the documentation doesn't explicitly address. Let's explore this problem and potential solutions based on the available information. You're right that onFinish expects a value of the type specified as the third type parameter of useForm, which in our case is FormType. This indeed creates a type safety issue when we try to submit ApiType data. Currently, there isn't a built-in solution in Refine's useForm hook to handle this scenario with full type safety. However, there are a couple of approaches we can consider: 1. Use type assertion (type casting): This isn't ideal from a type safety perspective, but it can be a temporary workaround:
const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
onFinish(apiData as unknown as FormType);
};

const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
onFinish(apiData as unknown as FormType);
};

2. Create a union type: As mentioned in a comment by a Refine team member in the GitHub issue discussion, you could use a union type:
const { formProps, saveButtonProps, onFinish, queryResult } = useForm<
ApiType,
HttpError,
FormType | ApiType
>();

const { formProps, saveButtonProps, onFinish, queryResult } = useForm<
ApiType,
HttpError,
FormType | ApiType
>();

This approach allows both FormType and ApiType to be valid for onFinish, but it doesn't enforce the correct type at compile time. It's worth noting that the Refine team is aware of this limitation. In the same GitHub issue, they mention that they're considering improving the type system for useForm. One proposed solution involves adding a fourth type parameter for submission data:
useForm<User, HttpError, FormProps, User>
useForm<User, HttpError, FormProps, User>
However, this feature is not yet implemented. For now, the most type-safe approach would be to handle the transformation within the component and use FormType consistently:
const CreatePage = () => {
const { formProps, saveButtonProps, onFinish, queryResult } = useForm<
ApiType,
HttpError,
FormType
>();

const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
// Here we're still working with FormType, maintaining type safety
onFinish(formData);
};

// Rest of the component...
};
const CreatePage = () => {
const { formProps, saveButtonProps, onFinish, queryResult } = useForm<
ApiType,
HttpError,
FormType
>();

const handleOnFinish = (formData: FormType) => {
const apiData: ApiType = {
fullName: `${formData.name} ${formData.surname}`,
};
// Here we're still working with FormType, maintaining type safety
onFinish(formData);
};

// Rest of the component...
};
Then, you would handle the transformation from FormType to ApiType in your data provider or in a custom useResource hook. I apologize again for the confusion in my previous responses. This is indeed a limitation in the current implementation of useForm in Refine, and it's good that you've identified this issue. If this is a significant problem for your use case, you might want to consider opening an issue on the Refine GitHub repository to request a more robust solution for handling different types for form data and API data. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2d ago
<@242226786145009665> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?