Skip to content

Commit 66aa3ab

Browse files
committed
Add support for application/json and txt/plain serialization
1 parent f963974 commit 66aa3ab

File tree

4 files changed

+237
-35
lines changed

4 files changed

+237
-35
lines changed

packages/react-router-dom/dom.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,18 @@ export function getFormSubmissionInfo(
253253
`Cannot submit element that is not <form>, <button>, or ` +
254254
`<input type="submit|image">`
255255
);
256-
} else if (options.encType === null) {
256+
} else if (
257+
options.encType !== undefined &&
258+
options.encType !== "application/x-www-form-urlencoded" &&
259+
options.encType !== "multipart/form-data"
260+
) {
261+
// Any encType other than these (null, application/json, text/plain) means
262+
// we will not be submitting as FormData so send the payload through. The
263+
// @remix-run/router will handle serialization of the payload upon Request
264+
// creation if needed
257265
method = options.method || defaultMethod;
258266
action = options.action || null;
259-
encType = null;
267+
encType = options.encType;
260268
payload = target;
261269
} else {
262270
method = options.method || defaultMethod;
@@ -273,6 +281,8 @@ export function getFormSubmissionInfo(
273281
formData.append(name, value);
274282
}
275283
} else if (target != null) {
284+
// To be deprecated in v7 so the default behavior of undefined matches
285+
// the null behavior of no-serialization
276286
for (let name of Object.keys(target)) {
277287
// @ts-expect-error
278288
formData.append(name, target[name]);

packages/router/__tests__/router-test.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4834,6 +4834,7 @@ describe("a router", () => {
48344834
await t.navigate("/tasks", {
48354835
// @ts-expect-error
48364836
formMethod: "head",
4837+
// @ts-expect-error
48374838
formData: formData,
48384839
});
48394840
expect(t.router.state.navigation.state).toBe("idle");
@@ -4874,6 +4875,7 @@ describe("a router", () => {
48744875
await t.navigate("/tasks", {
48754876
// @ts-expect-error
48764877
formMethod: "options",
4878+
// @ts-expect-error
48774879
formData: formData,
48784880
});
48794881
expect(t.router.state.navigation.state).toBe("idle");
@@ -6006,6 +6008,161 @@ describe("a router", () => {
60066008
expect((await req.formData()).get("file")).toEqual("file.txt");
60076009
});
60086010

6011+
it("serializes payload as application/x-www-form-urlencoded", async () => {
6012+
let t = setup({
6013+
routes: [{ id: "root", path: "/", action: true }],
6014+
});
6015+
6016+
let payload = { a: "1" };
6017+
let nav = await t.navigate("/", {
6018+
formMethod: "post",
6019+
formEncType: "application/x-www-form-urlencoded",
6020+
payload,
6021+
});
6022+
await nav.actions.root.resolve("ACTION");
6023+
6024+
expect(nav.actions.root.stub).toHaveBeenCalledWith({
6025+
params: {},
6026+
request: expect.any(Request),
6027+
payload,
6028+
});
6029+
6030+
let request = nav.actions.root.stub.mock.calls[0][0].request;
6031+
expect(request.method).toBe("POST");
6032+
expect(request.url).toBe("http://localhost/");
6033+
expect(request.headers.get("Content-Type")).toBe(
6034+
"application/x-www-form-urlencoded;charset=UTF-8"
6035+
);
6036+
expect((await request.formData()).get("a")).toBe("1");
6037+
});
6038+
6039+
it("serializes payload as application/json if specified (object)", async () => {
6040+
let t = setup({
6041+
routes: [{ id: "root", path: "/", action: true }],
6042+
});
6043+
6044+
let payload = { a: "1" };
6045+
let nav = await t.navigate("/", {
6046+
formMethod: "post",
6047+
formEncType: "application/json",
6048+
payload,
6049+
});
6050+
await nav.actions.root.resolve("ACTION");
6051+
6052+
expect(nav.actions.root.stub).toHaveBeenCalledWith({
6053+
params: {},
6054+
request: expect.any(Request),
6055+
payload,
6056+
});
6057+
6058+
let request = nav.actions.root.stub.mock.calls[0][0].request;
6059+
expect(request.method).toBe("POST");
6060+
expect(request.url).toBe("http://localhost/");
6061+
expect(request.headers.get("Content-Type")).toBe("application/json");
6062+
expect(JSON.parse(await request.text())).toEqual(payload);
6063+
});
6064+
6065+
it("serializes payload as application/json if specified (array)", async () => {
6066+
let t = setup({
6067+
routes: [{ id: "root", path: "/", action: true }],
6068+
});
6069+
6070+
let payload = [1, 2, 3];
6071+
let nav = await t.navigate("/", {
6072+
formMethod: "post",
6073+
formEncType: "application/json",
6074+
payload,
6075+
});
6076+
await nav.actions.root.resolve("ACTION");
6077+
6078+
expect(nav.actions.root.stub).toHaveBeenCalledWith({
6079+
params: {},
6080+
request: expect.any(Request),
6081+
payload,
6082+
});
6083+
6084+
let request = nav.actions.root.stub.mock.calls[0][0].request;
6085+
expect(request.method).toBe("POST");
6086+
expect(request.url).toBe("http://localhost/");
6087+
expect(request.headers.get("Content-Type")).toBe("application/json");
6088+
expect(JSON.parse(await request.text())).toEqual(payload);
6089+
});
6090+
6091+
it("serializes payload as text/plain if specified", async () => {
6092+
let t = setup({
6093+
routes: [{ id: "root", path: "/", action: true }],
6094+
});
6095+
6096+
let payload = "plain text";
6097+
let nav = await t.navigate("/", {
6098+
formMethod: "post",
6099+
formEncType: "text/plain",
6100+
payload,
6101+
});
6102+
await nav.actions.root.resolve("ACTION");
6103+
6104+
expect(nav.actions.root.stub).toHaveBeenCalledWith({
6105+
params: {},
6106+
request: expect.any(Request),
6107+
payload,
6108+
});
6109+
6110+
let request = nav.actions.root.stub.mock.calls[0][0].request;
6111+
expect(request.method).toBe("POST");
6112+
expect(request.url).toBe("http://localhost/");
6113+
expect(request.headers.get("Content-Type")).toBe("text/plain");
6114+
expect(await request.text()).toEqual(payload);
6115+
});
6116+
6117+
it("does not serialize payload when encType=undefined", async () => {
6118+
let t = setup({
6119+
routes: [{ id: "root", path: "/", action: true }],
6120+
});
6121+
6122+
let payload = { a: "1" };
6123+
let nav = await t.navigate("/", {
6124+
formMethod: "post",
6125+
payload,
6126+
});
6127+
await nav.actions.root.resolve("ACTION");
6128+
6129+
expect(nav.actions.root.stub).toHaveBeenCalledWith({
6130+
params: {},
6131+
request: expect.any(Request),
6132+
payload,
6133+
});
6134+
6135+
let request = nav.actions.root.stub.mock.calls[0][0].request;
6136+
expect(request.method).toBe("POST");
6137+
expect(request.body).toBe(null);
6138+
expect(request.headers.get("Content-Type")).toBe(null);
6139+
});
6140+
6141+
it("does not serialize payload when encType=null", async () => {
6142+
let t = setup({
6143+
routes: [{ id: "root", path: "/", action: true }],
6144+
});
6145+
6146+
let payload = { a: "1" };
6147+
let nav = await t.navigate("/", {
6148+
formMethod: "post",
6149+
formEncType: null,
6150+
payload,
6151+
});
6152+
await nav.actions.root.resolve("ACTION");
6153+
6154+
expect(nav.actions.root.stub).toHaveBeenCalledWith({
6155+
params: {},
6156+
request: expect.any(Request),
6157+
payload,
6158+
});
6159+
6160+
let request = nav.actions.root.stub.mock.calls[0][0].request;
6161+
expect(request.method).toBe("POST");
6162+
expect(request.body).toBe(null);
6163+
expect(request.headers.get("Content-Type")).toBe(null);
6164+
});
6165+
60096166
it("races actions and loaders against abort signals", async () => {
60106167
let loaderDfd = createDeferred();
60116168
let actionDfd = createDeferred();

0 commit comments

Comments
 (0)