redux-toolkit: RTK Query: Difficulties wrangling `content-type: 'multipart/form-data'` headers with form data bodies
I have an api definition based mostly off the examples in the docs, in full below. I have an endpoint I want to add that involves uploading a file to the server. In Axios, the api call looks like this:
const data = new FormData();
data.append('image', file);
await axios.post(URLS.IMAGE(), data, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
This works, despite me not defining a boundary
. I don’t know why. edit: It seems axios automatically adds form data boundary: multipart/form-data; boundary=---------------------------35214638641989039512767343688
If I attempt the same thing with an RTK mutation:
uploadImage: builder.mutation<
string,
{ payload: FormData}
>({
query: ({ payload }) => {
return {
url: URLS.IMAGE(),
method: 'POST',
body: payload,
headers: {
'Content-Type': 'multipart/form-data;',
},
};},
}),
I get this error from Django: Multipart form parse error - Invalid boundary in multipart: None
.
While I haven’t guaranteed this issue isn’t due to Django itself, since it works with axios, and not RTK’s baseQuery
, I feel I’m doing something wrong with RTK.
I tried removing the headers, and they set a default content-type
of application/json
, as per the docs: https://redux-toolkit.js.org/rtk-query/api/fetchBaseQuery#using-fetchbasequery . This obviously causes an error from the server.
I tried adding a boundary myself that I found online, a big string like webkit---easdfajf
but that was unrecognized as correct by Django - it didn’t parse out the data correctly, it seems, which I believe means I guessed the wrong boundary.
Many solutions online suggest deleting the content-type
header and allowing fetch
to set it itself when it detects FormData
in the body, however this doesn’t seem to be an option with RTK baseQuery
.
A potential solution could be to use a queryFn
with axios
, but I want to avoid that so I can remove axios
entirely from my project, and also so I don’t have to handle token setting in some other spot (plus token refresh) arbitrarily.
I could also use queryFn
with default fetch
api for this one endpoint, but, the issue with token refresh remains, I don’t want to have to re-handle it just for this one endpoint if possible.
Am I missing something in configuration?
Api definition:
const baseQuery = fetchBaseQuery({
baseUrl: API_URL,
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).user.token;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
});
const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
await mutex.waitForUnlock();
const moddedArgs = args;
if (args?.body) {
moddedArgs.body = snakeCase(removeNullish(args.body));
}
let result = await baseQuery(args, api, extraOptions);
if (
result.error &&
result.error.status === 401 &&
result.error.data?.code !== 'no_active_account' &&
result.error.data?.code !== 'not_authenticated'
) {
if (!mutex.isLocked()) {
const release = await mutex.acquire();
try {
const refreshResult = await baseQuery(
{
url: URLS.REFRESH_TOKEN,
method: 'POST',
body: {
refresh: (api.getState() as RootState).user.refreshToken,
},
},
api,
extraOptions
);
if (refreshResult?.data?.access) {
api.dispatch(setToken(refreshResult.data.access as string));
result = await baseQuery(
moddedArgs as string | FetchArgs,
api,
extraOptions
);
} else {
api.dispatch(setToken(null));
api.dispatch(setRefreshToken(null));
}
} finally {
release();
}
} else {
await mutex.waitForUnlock();
result = await baseQuery(
moddedArgs as string | FetchArgs,
api,
extraOptions
);
}
}
if (result.data) {
result.data = camelize(result.data);
}
if (result.error) {
result.error = camelize(result.error);
}
return result;
};
// Our endpoints are all injected
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: baseQueryWithReauth,
endpoints: (builder) => ({}),
tagTypes: [
...
],
});
edit3: Setting Content-Type
to undefined
in headers
merely results in the Content-Type
being set to application/json
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 16 (5 by maintainers)
Just make sure you are not setting any “Content-type” header for the POST request. This fixed it for me.
You can modify your RTK api as : query: ({ jobId, formData }) => ({ url:
your url
, method: “POST”, body: formData, formData: true, }),