inertia icon indicating copy to clipboard operation
inertia copied to clipboard

Append empty array to formdata

Open RobertBoes opened this issue 2 years ago • 5 comments

Summary

Currently empty arrays aren't appended to the FormData object, this means sending an empty array won't reach the server at all. It can be confusing when you have a form with an optional file, like so:

const form = useForm({
  image: null,
  tags: [],
});

When the image is null, the tags property is received by Laravel as an empty array []. However, when a file is present Inertia converts the object to a FormData object. Now the tags property won't be sent to the server at all, not even as a null value.

FormData limitations

With FormData it seems to be impossible to send an empty array, at least with Laravel. For example doing form.append('mykey', []) results in the following:

// $request->all();
[
  "mykey" => "[]"
]

Doing form.append('mykey[]', '') results in:

// $request->all();
[
  "mykey" => [
    0 => null,
  ]
]

Sending a "normal" XHR request with a JSON payload would contain the following and results in an empty array: { "mykey": [] }

This implementation send the key as an empty string, same as null and undefined values, so $request->all() will result in:

[
  "mykey": null,
]

RobertBoes avatar Aug 21 '21 00:08 RobertBoes

Hey @RobertBoes,

I'm not sure what the correct behaviour here would be, actually. I don't want to do something just because Laravel does or does not do it, but I also do agree that receiving it conditionally based on some arbitrary condition such as there being a file is a bit strange.

As such, I'm going to leave this one open for now with the given labels attached, so we can think this over at a later point.

Thanks!

claudiodekker avatar Dec 01 '21 18:12 claudiodekker

Thanks Robert for the PR ! I was watching this thread since I've reported this issue and was hoping the fix would go through. Anyway I'm currently using a hack to get around this issue. Which is right before the validation I check for the array and set it if it doesn't exist :

$this->request->input('tags') ? false : $this->request->merge(['tags' => []]);

It's a bit unpractical but it gets the job done, I guess.

HassanZahirnia avatar Dec 01 '21 19:12 HassanZahirnia

I think ideally empty arrays would still be sent (as an empty array, not as a null/empty string), since they're not NOTHING, just empty.

If I have a PUT endpoint which updates only form fields that are sent in the request, and leave the remaining fields untouched (partial modifications) then without this, it would be impossible to set an array to empty, since the backend would never receive the empty array and therefor know to update it to []. Checking for existence of the field and setting to empty if not set would mean its always required to send the full array even if you're attempting to update a completely different field.

Consider this pseudo-api for users and teams:

PUT /api/users/1

{
    "name": "Test"
}

Should only update the user name, but if we check for existence of for example teams and resetting if not set, then when updating the user name, we reset their teams unintentionally.

Instead this would be the correct payload to remove the user from all teams.

PUT /api/users/1

{
    "teams": []
}

LasseRafn avatar Jan 23 '22 16:01 LasseRafn

I think ideally empty arrays would still be sent (as an empty array, not as a null/empty string), since they're not NOTHING, just empty.

If I have a PUT endpoint which updates only form fields that are sent in the request, and leave the remaining fields untouched (partial modifications) then without this, it would be impossible to set an array to empty, since the backend would never receive the empty array and therefor know to update it to []. Checking for existence of the field and setting to empty if not set would mean its always required to send the full array even if you're attempting to update a completely different field.

Consider this pseudo-api for users and teams:

PUT /api/users/1

{
    "name": "Test"
}

Should only update the user name, but if we check for existence of for example teams and resetting if not set, then when updating the user name, we reset their teams unintentionally.

Instead this would be the correct payload to remove the user from all teams.

PUT /api/users/1

{
    "teams": []
}

This PR was about Inertia's automatic conversion to FormData when the form contains a file. Empty arrays are sent by Inertia, unless it's being converted to FormData. There is also a limitation there, because FormData doesn't have something as an empty array, you simply can't send an empty array, so that value won't be sent at all. My proposed solution was to send it as null value so the server would still receive the key, instead of not receiving it at all. But again, this only applies when the form contains a file and Inertia converts the request to FormData

RobertBoes avatar Jan 23 '22 16:01 RobertBoes

@RobertBoes ahh got it, so basically to replicate the default (no-file in form) behavior as closely as possible

LasseRafn avatar Jan 23 '22 17:01 LasseRafn