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/form on submit content type #11615

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -118,7 +118,7 @@
"files": [
{
"path": "./dist/index.cjs.js",
"maxSize": "10.1 kB"
"maxSize": "10.2 kB"
}
]
},
Expand Down
4 changes: 1 addition & 3 deletions reports/api-extractor.md
Expand Up @@ -334,8 +334,6 @@ export type FormStateSubjectRef<TFieldValues extends FieldValues> = Subject<Part
export type FormSubmitHandler<TFieldValues extends FieldValues> = (payload: {
data: TFieldValues;
event?: React_2.BaseSyntheticEvent;
formData: FormData;
formDataJson: string;
method?: 'post' | 'put' | 'delete';
}) => unknown | Promise<unknown>;

Expand Down Expand Up @@ -871,7 +869,7 @@ export type WatchObserver<TFieldValues extends FieldValues> = (value: DeepPartia

// Warnings were encountered during analysis:
//
// src/types/form.ts:444:3 - (ae-forgotten-export) The symbol "Subscription" needs to be exported by the entry point index.d.ts
// src/types/form.ts:442:3 - (ae-forgotten-export) The symbol "Subscription" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
4 changes: 1 addition & 3 deletions src/__tests__/form.test.tsx
Expand Up @@ -93,10 +93,8 @@ describe('Form', () => {
<Form
encType={'application/json'}
action={'/success'}
onSubmit={({ data, formData, formDataJson }) => {
onSubmit={({ data }) => {
data;
formData;
formDataJson;
onSubmit();
}}
control={control}
Expand Down
30 changes: 30 additions & 0 deletions src/__tests__/logic/getRequestBody.test.ts
@@ -0,0 +1,30 @@
import getRequestBody from '../../logic/getRequestBody';

const data = {
a: 'foo',
b: ' ',
};

describe('getRequestBody', () => {
it('should return correct value when content type is application/x-www-form-urlencoded', () => {
expect(getRequestBody('application/x-www-form-urlencoded', data)).toBe(
'a=foo&b=+',
);
});

it('should return correct value when content type is application/json', () => {
expect(getRequestBody('application/json', data)).toBe(
'{"a":"foo","b":" "}',
);
});

it('should return correct value when content type is text/plain', () => {
expect(getRequestBody('text/plain', data)).toBe('a:foo\nb: ');
});

it('should return correct value when content type is multipart/form-data', () => {
const formData = getRequestBody('multipart/form-data', data);
expect(formData.get('a')).toBe('foo');
expect(formData.get('b')).toBe(' ');
});
});
28 changes: 9 additions & 19 deletions src/form.tsx
@@ -1,5 +1,6 @@
import React from 'react';

import getRequestBody from './logic/getRequestBody';
import get from './utils/get';
import { FieldValues, FormProps } from './types';
import { useFormContext } from './useFormContext';
Expand Down Expand Up @@ -54,41 +55,30 @@ function Form<
let type = '';

await control.handleSubmit(async (data) => {
const formData = new FormData();
let formDataJson = '';

try {
formDataJson = JSON.stringify(data);
} catch {}

for (const name of control._names.mount) {
formData.append(name, get(data, name));
}

if (onSubmit) {
await onSubmit({
data,
event,
method,
formData,
formDataJson,
});
}

if (action) {
try {
const shouldStringifySubmissionData = [
headers && headers['Content-Type'],
encType,
].some((value) => value && value.includes('json'));
const mountData: FieldValues = {};
for (const name of control._names.mount) {
mountData[name] = get(data, name);
}

const contentType = (headers && headers['Content-Type']) || encType;

const response = await fetch(action, {
method,
headers: {
...headers,
...(encType ? { 'Content-Type': encType } : {}),
...(contentType ? { 'Content-Type': contentType } : {}),
},
body: shouldStringifySubmissionData ? formDataJson : formData,
body: getRequestBody(contentType, mountData),
});

if (
Expand Down
34 changes: 34 additions & 0 deletions src/logic/getRequestBody.ts
@@ -0,0 +1,34 @@
import { FieldValues } from '../types';

const getQueryData = <T extends FieldValues>(data: T) =>
new URLSearchParams(data).toString();

const getPlainData = <T extends FieldValues>(data: T) =>
Object.entries(data)
.map(([key, value]) => key + ':' + value)
.join('\n');

const getFormData = <T extends FieldValues>(data: T) =>
Object.entries(data).reduce((formData, [key, value]) => {
formData.append(key, value);
return formData;
}, new FormData());

const dictionary: Record<string, any> = {
'application/x-www-form-urlencoded': getQueryData,
'application/json': JSON.stringify,
'text/plain': getPlainData,
'multipart/form-data': getFormData,
};

export default <T extends FieldValues>(
contentType: string | undefined,
data: T,
) => {
const key =
contentType && contentType in dictionary
? contentType
: 'application/x-www-form-urlencoded';

return dictionary[key](data);
};
2 changes: 0 additions & 2 deletions src/types/form.ts
Expand Up @@ -73,8 +73,6 @@ export type SubmitHandler<TFieldValues extends FieldValues> = (
export type FormSubmitHandler<TFieldValues extends FieldValues> = (payload: {
data: TFieldValues;
event?: React.BaseSyntheticEvent;
formData: FormData;
formDataJson: string;
method?: 'post' | 'put' | 'delete';
}) => unknown | Promise<unknown>;

Expand Down