redux-toolkit: RTK formData PUT not working

I have this file input

<input
    type="file"
    id="desktop-user-photo"
    name="user-photo"
    className="absolute inset-0 w-full h-full opacity-0 cursor-pointer border-gray-300 rounded-xs"
    onChange={(e) =>  updatePicture(e.target.files ? e.target.files[0] : null)}
    />

that calls this method:

const [editPicture] = useEditPictureMutation();

const updatePicture = async (file: File | null) => {
  const formData = new FormData();
  formData.append('file', file);
  await editPicture({ formData }).unwrap();
}

here the API slice

editPicture: builder.mutation<Profile, { formData: FormData }>({
  query: ({ formData }) => ({
    url: `/picture`,
    method: 'PUT',
    body: formData,
  }),
  invalidatesTags: [PROFILE_TAG],
}),

When I’m calling this endpoint I have and error from BE: [Nest] Unexpected end of multipart data

image image

If I try to use the same endpoint with swagger it works image

I’m using “@reduxjs/toolkit”: “1.8.1”

Any ideas? Thank you in advance!

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 24 (4 by maintainers)

Most upvoted comments

Hmm. Actually, that may be part of the issue? I don’t think RTKQ is really meant for file uploads.

Also, I think it tries to send the body as JSON by default.

So, may be some combination of those two things - if you’re trying to send a file, you may have to alter the settings for the fetch send.

We don’t set headers when the body is FormData and file uploads should just work (I do this myself with RTKQ in many projects). You can see by the screenshots that it’s generating the form boundary and such as expected. This is most likely an API issue.

Reference: https://github.com/reduxjs/redux-toolkit/blob/master/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx#L675-L694

const dataForm = ({name= '', file= null}) => {
    var formdata = new FormData();
    formdata.append("name", name);
    formdata.append("name", file);
    return formdata
}
getFile: builder.mutation({
            query: () => ({
                url: 'get-file',
                method: 'post',
                prepareHeaders: (headers) => {
                    headers.set("Content-Type", "multipart/form-data");
                    return headers;
                },
                body: dataForm({
                    name: 'Dummy',
                    file: 'File',
                }),
            }),

This work

Coming here to say that, for me, I was also running into this issue and was driving me nuts, but it turned out to be because I was always setting the content-type to “application/json” within the prepareHeaders function, which overrides the default multipart-form headers.

To get around this, I updated my prepareHeaders code as follows:

const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({
    baseUrl: BASE_URL,
    prepareHeaders: (headers, { getState, endpoint }) => {

      const UPLOAD_ENDPOINTS = ['uploadEndpoint1', 'uploadEndpoint2'];

      if (!UPLOAD_ENDPOINTS.includes(endpoint)) {
        headers.set('content-type', 'application/json');
      }
      // ...rest of your header stuff, such as api token setting.
      return headers;
    },
  }),
  //... rest of the arguments for createApi
});

Couldn’t figure out if there is a way to pass extra parameters from an individual endpoint (like somehow providing ignoreDefaultHeaders: true), so this is what I’m rolling with for now.

Hope this helps someone else!

I was going crazy with wis issue and this is what worked for me:

First, I prepared the FormData like this (thanks @Zaidzularsya ):

const dataForm = ({ file= null }) => {
    const formData = new FormData();
    formData.append("archivo", file); // "archivo" is the key that expects my backend for the file
    return formData;
}

Then I have my Api (secured) like this:

export const yourNameApi = createApi({
    reducerPath: 'yourReducer', 
    baseQuery: fetchBaseQuery({
        baseUrl: urlBase,
        // headers: {'Content-Type': 'multipart/form-data' }, -> NOTICE THAT ARE COMMENTED. NO HEADERS!!
        prepareHeaders: ( headers ) => { 
            const token = // TOKEN FROM LOCAL STORAGE;
            if (token) {
                headers.set("x-token", `${token}`);
            }
            return headers;            
        }
    }),
    endpoints( builder ) {
        return {
            imageActividad: builder.mutation({
                query: ( data ) => {
                    return {
                        url: `yourURL/${ data.id }`,
                        method: 'PUT',                 
                        body: dataForm({
                            file: data.image
                        })
                    };
                },
            }),
        }
    }
});

Indeed @msutkowski your code looks very similar to mine, it should just work… I’ll check the API.

We are using nestjs and also from there looks so straight forward

I also tried different files, and from the swagger UI the upload works 😕

image

Hmm. Actually, that may be part of the issue? I don’t think RTKQ is really meant for file uploads. Also, I think it tries to send the body as JSON by default. So, may be some combination of those two things - if you’re trying to send a file, you may have to alter the settings for the fetch send.

We don’t set headers when the body is FormData and file uploads should just work (I do this myself with RTKQ in many projects). You can see by the screenshots that it’s generating the form boundary and such as expected. This is most likely an API issue.

Reference: https://github.com/reduxjs/redux-toolkit/blob/master/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx#L675-L694

Thanks a lot!

When uploading formData with RTK, content-type should be undefined - just do not set it in the headers

https://github.com/reduxjs/redux-toolkit/issues/2287#issuecomment-1163565291 - 100% right and super helpful

(upd.) I left a comment here despite the fact that the issue is closed, since this thread is well indexed in the search engine and here is the most competent and clear information on the topic

yes @phryneas you are right, also fails with fetch so is not RTK issue. I’ll close this now and thank you guys for your help!

@fs-innonova It is indeed an issue with fetch. You can use axios instead of baseQuery.

https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#axios-basequery.

I used modified version of this example and got it to work.