I've been banging my head on the desk for the last few minutes here due to this API request...
I have the following code:
Saga:
export function * registerFlow () {
while (true) {
const request = yield take(authTypes.SIGNUP_REQUEST)
console.log('authSaga request', request)
let response = yield call(authApi.register, request.payload)
console.log('authSaga response', response)
if (response.error) {
return yield put({ type: authTypes.SIGNUP_FAILURE, response })
}
yield put({ type: authTypes.SIGNUP_SUCCESS, response })
}
}
API request:
// Inject fetch polyfill if fetch is unsuported
if (!window.fetch) { const fetch = require('whatwg-fetch') }
const authApi = {
register (userData) {
fetch(`http://localhost/api/auth/local/register`, {
method : 'POST',
headers : {
'Accept' : 'application/json',
'Content-Type' : 'application/json'
},
body : JSON.stringify({
name : userData.name,
email : userData.email,
password : userData.password
})
})
.then(statusHelper)
.then(response => response.json())
.catch(error => error)
.then(data => data)
}
}
function statusHelper (response) {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
return Promise.reject(new Error(response.statusText))
}
}
export default authApi
The API request does return a valid object however the return from the Saga call always is undefined. Can anyone guide me to where I am wrong?
Thanks in advance!
Best Regards,
Bruno
You forgot to return the promises from your function. Make it
const authApi = {
register (userData) {
return fetch(`http://localhost/api/auth/local/register`, {
// ^^^^^^
method : 'POST',
headers : {
'Accept' : 'application/json',
'Content-Type' : 'application/json'
},
body : JSON.stringify({
name : userData.name,
email : userData.email,
password : userData.password
})
})
.then(statusHelper)
.then(response => response.json());
}
};
Related
I am working on login feature and have problem when refresh token.
When token expire making request to refresh token, remove the old token, and save the new token to AsyncStorage.
After login successfully have to function A and B. The function A is using the new token to make its request. the function B say that it need to refresh the token so make request to refresh token ( the request make successfully, token being refresh) but The token that request A is using now invalid - I think it happens due to asynchronous
This is my code that use to refresh token:
axiosInstance.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
if (error.response.status === CODE_TOKEN_EXPIRED) {
try {
const token = await authenticationService.getRefreshToken();
const response = await authenticationService.refreshToken(token);
await authenticationService.removeToken();
await authenticationService.storeToken(response.data.params.access_token);
await authenticationService.storeRefreshToken(response.data.params.refresh_token);
error.config.headers.Authorization = 'Bearer ' + response.data.params.access_token;
error.response.config.headers['Authorization'] = 'Bearer ' + response.data.params.access_token;
return axiosInstance(error.config);
} catch (err) {
console.log(2, err);
await authenticationService.removeToken();
navigationService.navigate('LoginForm');
}
}
return Promise.reject(error);
}
);
Anyone know how to handle which asynchronous call for refresh token?
First would be for you to check if you are changing token to the correct axios instance. It is necessary to change Authorization header on error.response config as you did, but also for main axios instance (if you have one) like so: axios.defaults.headers.common["Authorization"] = "Bearer " + access_token;
If it is multiple parallel requests going on that could possibly need to be postponed after token is refreshed issue and answer gets complex, but check this gist with full refresh logic with axios.
I have implemented the same scenario in fetch API. you can also do this same in axios API. Try this to avoid interceptor concept.
Api.ts
export const api = ({ method, url, body, isProtected = true }) => {
return new Promise((resolve, reject) => {
const payload = {
method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
};
if (body !== null) {
(payload as any).body = JSON.stringify(body);
}
/**
* "isProtected" is used for API call without authToken
*/
if (isProtected) {
AsyncStorage.getItem(ACCESS_TOKEN).then(accessKey => {
(payload.headers as any).Authorization = `Bearer ${accessKey}`;
fetch(url, payload)
.then((response: any) => {
/*
* 419 status denotes the timeout of authToken
*/
if (response.status == 419) {
// refresh token
AsyncStorage.getItem(REFRESH_TOKEN).then(refreshKey => {
const payloadRef = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + refreshKey
}
};
/*
* This call refresh the authToken using refreshing call to renew the authToken
*/
fetch(URL.baseUrl + "/refresh", payloadRef)
.then((response: any) => response.json())
.then(response => {
/*
* if refresh token expired. redirect to login page
*/
if (response.status !== codes.SUCCESS) {
if (!User.sessionOver) {
User.sessionOver = true;
Alert.alert(
'Alert',
'Session Timeout',
[
{
text: 'Get back to Login',
onPress: () => {
// get to Login page
}
}
],
{ cancelable: false }
);
}
} else if (response.status == codes.SUCCESS) {
/*
* If refresh token got refreshed and set it as authToken and retry the api call.
*/
AsyncStorage.setItem(ACCESS_TOKEN, response.payload.access_key).then(() => {
(payload.headers as any).Authorization = 'Bearer ' + response.payload.access_key;
fetch(url, payload)
.then(response => response.json())
.then(response => {
if (response.status == codes.SUCCESS) {
resolve(response);
}
})
.catch(error => {
reject(error);
});
});
}
});
});
} else {
resolve(response.json());
}
})
.catch(error => {
reject(error);
});
});
} else {
fetch(url, payload)
.then((response: any) => {
response = response.json();
resolve(response);
})
.catch(error => {
reject(error);
});
}
});
};
MovieService.ts
import { api } from '../services/api';
import { URL } from '../config/UrlConfig';
const getMovies = () => {
const method = 'GET';
const url = URL.baseUrl + '/v1/top/movies';
const body = null;
const isProtected = true;
return api({ method, url, body, isProtected });
};
export { getMovies };
Maybe it will helps - https://gist.github.com/ModPhoenix/f1070f1696faeae52edf6ee616d0c1eb
import axios from "axios";
import { settings } from "../settings";
import { authAPI } from ".";
const request = axios.create({
baseURL: settings.apiV1,
});
request.interceptors.request.use(
(config) => {
// Get token and add it to header "Authorization"
const token = authAPI.getAccessToken();
if (token) {
config.headers.Authorization = token;
}
return config;
},
(error) => Promise.reject(error)
);
let loop = 0;
let isRefreshing = false;
let subscribers = [];
function subscribeTokenRefresh(cb) {
subscribers.push(cb);
}
function onRrefreshed(token) {
subscribers.map((cb) => cb(token));
}
request.interceptors.response.use(undefined, (err) => {
const {
config,
response: { status },
} = err;
const originalRequest = config;
if (status === 401 && loop < 1) {
loop++;
if (!isRefreshing) {
isRefreshing = true;
authAPI.refreshToken().then((respaonse) => {
const { data } = respaonse;
isRefreshing = false;
onRrefreshed(data.access_token);
authAPI.setAccessToken(data.access_token);
authAPI.setRefreshToken(data.refresh_token);
subscribers = [];
});
}
return new Promise((resolve) => {
subscribeTokenRefresh((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(axios(originalRequest));
});
});
}
return Promise.reject(err);
});
export default request;
hello i have this class when i call Auth.isAuthenticated() from another component in react, it always return false (its the default value), even if the server return a 200 response, witch sets this.authenticated = true .
how do i use promises to make the method wait till the fetch call is finished then return the result
Edit:
i need the Boolean true or false to be returned so based on that, i can show or hide the component, all answers are helpful but i need a Boolean not a promise any help please
class Auth {
constructor() {
this.authenticated = false;
}
isAuthenticated() {
//get token from local storage if there is one
const jwttoken = localStorage.getItem('jwttoken');
const bearer = 'Bearer ' + jwttoken;
const data = new FormData();
// get the website backend main url from .env
const REACT_APP_URL = process.env.REACT_APP_URL
fetch(`${REACT_APP_URL}/api/auth/verify`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Authorization': bearer,
},
body: data
}).then(
(response) => {
response.json()
.then((res) => {
if (response.status === 200) {
this.authenticated = true;
}
if (response.status === 401) {
localStorage.removeItem('jwttoken');
this.authenticated = false;
}
})
}
).catch((err) => {
// console.log(err)
});
return this.authenticated;
}
}
export default new Auth();
and from another component i call Auth.isAuthenticated() === true
export const PrivateRoute = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={(props) => (
Auth.isAuthenticated() === true
? <Component {...props} />
: <Redirect to='/admin' />
)} />
)
}
Let's say you want to write a function that returns a promise and resolves when some action finishes (an API call for example). You could write something like this:
const myAsyncFunction = () =>{
return new Promise((resolve, reject) =>{
//Faking an API Call
setTimeout(() => resolve('data'), 400)
})
}
There you go! Now you have a function that returns a promise which will resolve in 400ms. Now you need to use either .then() method or async await statements.
const sideEffects = async () =>{
const result = await myAsyncFunction()
console.log(result) //'data'
}
If you don't want to do async/await you can have isAuthenticated return a promise.
isAuthenticated() {
return new Promise((resolve, reject) => {
//get token from local storage if there is one
const jwttoken = localStorage.getItem('jwttoken');
const bearer = 'Bearer ' + jwttoken;
const data = new FormData();
// get the website backend main url from .env
const REACT_APP_URL = process.env.REACT_APP_URL
fetch(`${REACT_APP_URL}/api/auth/verify`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Authorization': bearer,
},
body: data
}).then(
(response) => {
response.json()
.then((res) => {
if (response.status === 200) {
resolve(true)
}
if (response.status === 401) {
localStorage.removeItem('jwttoken');
resolve(false)
}
})
}
).catch((err) => {
// reject(err)
});
})
}
And inside an async function you could do let isAuthenticated = await isAuthenticated() or you can use .then and .catch outside of async function to return result
use await async
async isAuthenticated() {
//get token from local storage if there is one
const jwttoken = localStorage.getItem('jwttoken');
const bearer = 'Bearer ' + jwttoken;
const data = new FormData();
// get the website backend main url from .env
const REACT_APP_URL = process.env.REACT_APP_URL
const response = await fetch(`${REACT_APP_URL}/api/auth/verify`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Authorization': bearer,
},
body: data
});
const responseToJson = await response.json();
if (responseToJson.status === 200) {
this.authenticated = true;
}
if (responseToJson.status === 401) {
localStorage.removeItem('jwttoken');
this.authenticated = false;
}
return this.authenticated;
}
I have tried several ways for mocking this unit of my code but still, it doesn't work. I'm using create-react-app and jest for testing.
I have a function in admin adminSignup.js for sending data to my server(Node.js and Mongoose) for creating account:
/* eslint-disable no-undef */
function signup(user, cb) {
return fetch(`signup`, {
headers: {"Content-Type": "application/json"},
method: "POST",
body:JSON.stringify({
username: user.username,
email: user.email,
password: user.password,
picode: user.pincode,
building: user.building,
city: user.city,
state: user.state
}),
})
.then(checkStatus)
.then(parseJSON)
.then(cb)
.catch(err => console.log(err));
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(`HTTP Error ${response.statusText}`);
error.status = response.statusText;
error.response = response;
console.log(error); // eslint-disable-line no-console
throw error;
}
function parseJSON(response) {
return response.json();
}
const adminSignup = { signup };
export default adminSignup;
and I have called this in my component(RegisterPage.jsx) :
adminSignup.signup( user, response => {
this.setState({response: response});
console.log(response);
});
Now I want to write a mock for my signup call(adminSignup.js). But just wonder how can I do this?
I have tried Jest Fetch Mock for mock testing(it doesnt need to create mock file) and it's working but I'm not quite sure is it correct or no :
describe('testing api', () => {
beforeEach(() => {
fetch.resetMocks();
});
it('calls signup and returns message to me', () => {
expect.assertions(1);
fetch.mockResponseOnce(JSON.stringify('Account Created Successfully,Please Check Your Email For Account Confirmation.' ));
//assert on the response
adminSignup.signup({
"email" : "sample#yahoo.com",
"password" : "$2a$0yuImLGh1NIoJoRe8VKmoRkLbuH8SU6o2a",
"username" : "username",
"pincode" : "1",
"city" : "Sydney",
"building" : "1",
"state" : "NSW"
}).then(res => {
expect(res).toEqual('Account Created Successfully,Please Check Your Email For Account Confirmation.');
});
//assert on the times called and arguments given to fetch
expect(fetch.mock.calls.length).toEqual(1);
});
});
I really like to create a mock file and test with that but reading jest website is not working for me.
Thanks in advance.
I have found this way(using mock-http-server) for another POST request and it works for me:
userList.js:
async function getUser (id, cb) {
const response = await fetch(`/getUserById/${id}`, {
// headers: {"Content-Type": "application/json"},
method: "POST",
body:JSON.stringify({
id : id
}),
})
.then(checkStatus)
.then(parseJSON)
.then(cb)
.catch(err => console.log(err));
const user = response.json();
return user;
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(`HTTP Error ${response.statusText}`);
error.status = response.statusText;
error.response = response;
console.log(error); // eslint-disable-line no-console
throw error;
}
function parseJSON(response) {
return response.json();
}
}
userList.test.js:
import ServerMock from "mock-http-server";
import userLists from '../components/UserList/userLists';
describe('Test with mock-http-server', function() {
// Run an HTTP server on localhost:3000
var server = new ServerMock({ host: "localhost", port: 3000 });
beforeEach(function(done) {
server.start(done);
});
afterEach(function(done) {
server.stop(done);
});
it('should do something', function(done) {
var id = 4;
server.on({
method: 'POST',
path: `/getUserById/${id}`,
reply: {
status: 200,
headers: { "content-type": "application/json" },
body: JSON.stringify({ id: 4 })
}
});
// Now the server mock will handle a GET http://localhost:3000//getUserById/${id}
// and will reply with 200 `{"id": 4}`
function cb(data) {
console.log(data);
expect(data).toBe({name:'Bob'});
done();
}
const response = userLists.getUser(4, cb);
console.log(response);
done();
});
Had a look for this in the questions that offered but this was the closest and it didnt really address my problem.
I have a code block (detailed a little way down the page) as part of a larger fetch block.. it gets to this codeblock and also runs fine if this code block is commented out i.e it carrys out a successful fetch etc and returns a JWT no problem but... add this block in and i get the following error:
TypeError: (0 , _localStorageDropDowns.confirmSelectDataExistance)(...).then is not a function
It is referring to this function in another folder (imported correctly)..
export const confirmSelectDataExistance = () => {
const companyStateShortNameJson = localStorage.getItem(COMPANYSTATESHORTNAME)
const statesJson = localStorage.getItem(STATES)
const suburbLocationsJson = localStorage.getItem(LOCATIONS)
if (companyStateShortNameJson || statesJson || suburbLocationsJson) {
console.log('something exists in localstorage')
return true
}
console.log('nothing in localstorage')
return false
}
simple function - returns true or false.
and here is the code block -its failing on the first line:
return confirmSelectDataExistance().then(isConfirmed => {
if (!isConfirmed) {
dispatch({ type: REQUEST_SELECT_DATA })
console.log('gets here!', isConfirmed)
const token = getJwt()
const headers = new Headers({
'Authorization': `Bearer ${token}`
})
const retrieveSelectData = fetch('/api/SelectData/SelectData', {
method: 'GET',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
})
.then(handleErrors)
.then(response => response.json())
.then(selectData => {
dispatch({ type: RECEIVE_SELECT_DATA, payload: selectData })
saveSelectData(selectData)
});
return saveSelectData(selectData);
}
})
From my limited experience the "confirmSelectDataExistance" is a function so why is it saying that its not?
Finally here is the whole action in its entirety so you can see how it that block is called.. as I said - comment the block out and it works perfectly..
export const requestLoginToken = (username, password) =>
(dispatch, getState) => {
dispatch({ type: REQUEST_LOGIN_TOKEN, payload: username })
const payload = {
userName: username,
password: password,
}
const task = fetch('/api/jwt', {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
})
.then(handleErrors)
.then(response => response.json())
.then(data => {
dispatch({ type: RECEIVE_LOGIN_TOKEN, payload: data })
saveJwt(data)
return confirmSelectDataExistance().then(isConfirmed => {
if (!isConfirmed) {
dispatch({ type: REQUEST_SELECT_DATA })
console.log('gets here!', isConfirmed)
const token = getJwt()
const headers = new Headers({
'Authorization': `Bearer ${token}`
})
const retrieveSelectData = fetch('/api/SelectData/SelectData', {
method: 'GET',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
})
.then(handleErrors)
.then(response => response.json())
.then(selectData => {
dispatch({ type: RECEIVE_SELECT_DATA, payload: selectData })
saveSelectData(selectData)
});
return saveSelectData(selectData);
}
})
})
.catch(error => {
clearJwt()
console.log('ERROR - LOGIN!',error)
})
addTask(task)
return task
}
EDIT
I have finally got this to work after hacking away for hours.. Here is the finished action:
export const requestLoginToken = (username, password) =>
(dispatch, getState) => {
dispatch({ type: REQUEST_LOGIN_TOKEN, payload: username })
const payload = {
userName: username,
password: password,
}
const task = fetch('/api/jwt', {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
})
.then(handleErrors)
.then(response => response.json())
.then(data => {
dispatch({ type: RECEIVE_LOGIN_TOKEN, payload: data })
saveJwt(data)
// Now check local storage for dropdown data..
if (!confirmSelectDataExistance()) {
dispatch({ type: REQUEST_SELECT_DATA })
const token = JSON.stringify(data)
const headers = new Headers({
'Authorization': `Bearer ${token}`
})
const retrieveSelectData = fetch('/api/SelectData/SelectData', {
method: 'GET',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
})
.then(handleErrors)
.then(response => response.json())
.then(selectData => {
dispatch({ type: RECEIVE_SELECT_DATA, payload: selectData })
saveSelectData(selectData)
});
}
})
.catch(error => {
clearJwt()
console.log('ERROR - LOGIN!', error)
})
addTask(task)
return task
}
and here is the function it calls:
export const confirmSelectDataExistance = () => {
const companyStateShortNameJson = localStorage.getItem(COMPANYSTATESHORTNAME)
const statesJson = localStorage.getItem(STATES)
const suburbLocationsJson = localStorage.getItem(LOCATIONS)
if (companyStateShortNameJson || statesJson || suburbLocationsJson) {
console.log('something exists in localstorage')
return true
}
console.log('nothing in localstorage')
return false
}
The one thing I changed from the other attempts is that I used "data" instead of calling "getJwt()". I then used the line:
const token = JSON.stringify(data)
to obtain the JWT I just got.
In the end I used #Karin s answer and ran with that. (upvoted by me)
The error is not saying that confirmSelectDataExistance is not a function, it's saying that then isn't a function on what is returned from it, which is a boolean (it would be equivalent to false.then(...), which doesn't work).
If seems like you're trying to use then as a conditional. In that case a simple if statement should work:
if (confirmSelectDataExistance()) {
// do things if it returns true
} else {
// do things if it returns false
}
export const confirmSelectDataExistance = () => {
return new Promise(function (resolve, reject) {
const companyStateShortNameJson = localStorage.getItem(COMPANYSTATESHORTNAME)
const statesJson = localStorage.getItem(STATES)
const suburbLocationsJson = localStorage.getItem(LOCATIONS)
if (companyStateShortNameJson || statesJson || suburbLocationsJson) {
console.log('something exists in localstorage')
resolve(true)
}
console.log('nothing in localstorage')
reject(false)
})
}
Try something like this:
export const confirmSelectDataExistance = new Promise((resolve, reject) => {
const companyStateShortNameJson = localStorage.getItem(COMPANYSTATESHORTNAME);
const statesJson = localStorage.getItem(STATES);
const suburbLocationsJson = localStorage.getItem(LOCATIONS);
if (companyStateShortNameJson || statesJson || suburbLocationsJson) {
console.log('something exists in localstorage');
resolve(true);
}
console.log('nothing in localstorage');
reject(false); // or resolve(false) if you want handle this situation inside then block also
});
During implementing login feature with React, Redux, isomorphic-fetch, ES6 Babel.
Questions
I do not know how to properly combine promises after the checkstatus promise in order to get parsed JSON data from my server.
what am I doing wrong here?
also, do I need to replace isomorphic-fetch package with other more convenient one?
any suggestion for other package is welcome!
loginAction.js
import * as API from '../middleware/api';
import * as ActionTypes from '../actionTypes/authActionTypes';
import 'isomorphic-fetch';
function encodeCredentials(id, pwd) {
return btoa(`${id}{GS}${pwd}`);
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
response;
} else {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
}
function parseJSON(response) {
return response.json();
}
export function loginFailure(error) {
return { error, type: ActionTypes.LOGIN_FAILURE };
}
export function loginSuccess(response) {
return dispatch => {
dispatch({ response, type: ActionTypes.LOGIN_SUCCESS });
};
}
export function loginRequest(id, pwd) {
return {
type: ActionTypes.LOGIN_REQUEST,
command: 'login',
lang: 'en',
str: encodeCredentials(id, pwd),
ip: '',
device_id: '',
install_ver: '',
};
}
export function login(id, pwd) {
const credentials = loginRequest(id, pwd);
return dispatch => {
fetch(`${API.ROOT_PATH}${API.END_POINT.LOGIN}`, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
})
.then(checkStatus)
.then(parseJSON)
.then(data => {
console.log(`parsed data ${data}`);
dispatch(loginSuccess(data));
})
.catch(error => {
console.log(`request failed ${error}`);
});
};
}
In my projects usually, I have a helper function fetchJSON that does all utility logic, such as JSON parsing and status check.
Here it is:
import fetch from 'isomorphic-fetch';
function checkStatus(response) {
if(response.ok) {
return response;
} else {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
}
function parseJSON(response) {
return response.json();
}
export default function enhancedFetch(url, options) {
options.headers = Object.assign({
'Accept': 'application/json',
'Content-Type': 'application/json'
}, options.headers);
if(typeof options.body !== 'string') {
options.body = JSON.stringify(options.body);
}
return fetch(url, options)
.then(checkStatus)
.then(parseJSON);
}
Then you can use it in actions:
import fetchJSON from '../utils/fetchJSON'; // this is the enhanced method from utilities
export function login(id, pwd) {
const credentials = loginRequest(id, pwd);
return dispatch => {
fetchJSON(`${API.ROOT_PATH}${API.END_POINT.LOGIN}`, {
method: 'post',
body: credentials
}).then(data => {
console.log(`parsed data ${data}`);
dispatch(loginSuccess(data));
}).catch(error => {
console.log(`request failed ${error}`);
});
};
}
It helps you to keep actions code clean from some boilerplate code. In big projects with tons of similar fetch calls it is a really must-have thing.
You're doing it right, you just forgot return in checkstatus; you should return the response such that the next promise in the chain can consume it.
Also, it seems that checkstatus is synchronous operation, so it's no need to chain it by .then (although, it's OK if you like it that way), you can write:
fetch(...)
.then(response=>{
checkStatus(response)
return response.json()
})
.then(data=>{
dispatch(loginSuccess(data))
})
.catch(...)
I see no reason to get rid of isomorphic-fetch for now - it seems that it does its job.