I am modelling the auth layer for a simple react/redux app. On the server side I have an API based on the devise_token_auth gem.
I am using fetch
to post a sign in request:
const JSON_HEADERS = new Headers({
'Content-Type': 'application/json'
});
export const postLogin = ({ email, password }) => fetch(
`${API_ROOT}/v1/auth/sign_in`, {
method: 'POST',
headers: JSON_HEADERS,
body: JSON.stringify({ email, password })
});
// postLogin({ email: '[email protected]', password: 'whatever' });
This works, and I get a 200 response and all the data I need. My problem is, information is divided between the response body and headers.
- Body: user info
- Headers: access-token, expiration, etc.
I could parse the JSON body this way:
postLogin({ '[email protected]', password: 'whatever' })
.then(res => res.json())
.then(resJson => dispatch(myAction(resJson))
But then myAction
would not get any data from the headers (lost while parsing JSON).
Is there a way to get both headers and body from a fetch
Request?
Thanks!
I am modelling the auth layer for a simple react/redux app. On the server side I have an API based on the devise_token_auth gem.
I am using fetch
to post a sign in request:
const JSON_HEADERS = new Headers({
'Content-Type': 'application/json'
});
export const postLogin = ({ email, password }) => fetch(
`${API_ROOT}/v1/auth/sign_in`, {
method: 'POST',
headers: JSON_HEADERS,
body: JSON.stringify({ email, password })
});
// postLogin({ email: '[email protected]', password: 'whatever' });
This works, and I get a 200 response and all the data I need. My problem is, information is divided between the response body and headers.
- Body: user info
- Headers: access-token, expiration, etc.
I could parse the JSON body this way:
postLogin({ '[email protected]', password: 'whatever' })
.then(res => res.json())
.then(resJson => dispatch(myAction(resJson))
But then myAction
would not get any data from the headers (lost while parsing JSON).
Is there a way to get both headers and body from a fetch
Request?
Thanks!
4 Answers
Reset to default 11I thought I'd share the way we finally solved this problem: by just adding a step in the .then
chain (before parsing the JSON) to parse the auth headers and dispatch the proper action:
fetch('/some/url')
.then(res => {
const authHeaders = ['access-token', 'client', 'uid']
.reduce((result, key) => {
let val = res.headers.get(key);
if (val) {
result[key] = val;
}
}, {});
store.dispatch(doSomethingWith(authHeaders)); // or localStorage
return res;
})
.then(res => res.json())
.then(jsonResponse => doSomethingElseWith(jsonResponse))
One more approach, inspired by the mighty Dan Abramov (http://stackoverflow./a/37099629/1463770)
fetch('/some/url')
.then(res => res.json().then(json => ({
headers: res.headers,
status: res.status,
json
}))
.then({ headers, status, json } => goCrazyWith(headers, status, json));
HTH
Using async/await:
const res = await fetch('/url')
const json = await res.json()
doSomething(headers, json)
Without async/await:
fetch('/url')
.then( res => {
const headers = res.headers.raw())
return new Promise((resolve, reject) => {
res.json().then( json => resolve({headers, json}) )
})
})
.then( ({headers, json}) => doSomething(headers, json) )
This approach with Promise
is more general. It is working in all cases, even when it is inconvenient to create a closure that captures res
variable (as in the other answer here). For example when handlers is more plex and extracted (refactored) to separated functions.
My solution for the WP json API
fetch(getWPContent(searchTerm, page))
.then(response => response.json().then(json => ({
totalPages: response.headers.get("x-wp-totalpages"),
totalHits: response.headers.get("x-wp-total"),
json
})))
.then(result => {
console.log(result)
})
If you want to parse all headers into an object (rather than keeping the Iterator) you can do as follows (based on Dan Abramov's approach above):
fetch('https://jsonplaceholder.typicode./users')
.then(res => (res.headers.get('content-type').includes('json') ? res.json() : res.text())
.then(data => ({
headers: [...res.headers].reduce((acc, header) => {
return {...acc, [header[0]]: header[1]};
}, {}),
status: res.status,
data: data,
}))
.then((headers, status, data) => console.log(headers, status, data)));
or within an async
context/function:
let response = await fetch('https://jsonplaceholder.typicode./users');
const data = await (
response.headers.get('content-type').includes('json')
? response.json()
: response.text()
);
response = {
headers: [...response.headers].reduce((acc, header) => {
return {...acc, [header[0]]: header[1]};
}, {}),
status: response.status,
data: data,
};
will result in:
{
data: [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}],
headers: {
cache-control: "public, max-age=14400"
content-type: "application/json; charset=utf-8"
expires: "Sun, 23 Jun 2019 22:50:21 GMT"
pragma: "no-cache"
},
status: 200
}
depending on your use case this might be more convenient to use. This solution also takes into account the content-type to call either .json()
or .text()
on the response.