I use in my react application redux saga. There i have a login form. With redux saga i try to handle the error when user login.
Bellow is my saga:
function* postLoginUserReq(user) {
const {name} = user.values.user;
try {
const data = yield call(() => {
return fetch("url", {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: name,
}),
}).then(data => data.json()).then(response => {
userErrorLogIn(response.error) //here i check if appears an error
})
});
} catch (error) {
console.log(error);
}
}
Bellow is action creator:
export const userErrorLogIn = (error) => {
console.log(error) //the error message appears here
return {
type: USER_ERROR_LOGIN,
payload: error
};
};
Bellow is reducer:
case USER_ERROR_LOGIN: {
console.log(action.payload) //here the error message does not appears (why?)
return {
...state,
userIsLoggedError:action.payload,
}
}
Question: What could be the issue that i don't get the error in reducer?
You can use .catch for that -
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
fetch("http://httpstat.us/500")
.then(handleErrors)
.then(response => console.log("ok") )
.catch(error => userErrorLogIn(error) );
https://www.tjvantoll.com/2015/09/13/fetch-and-errors/
Related
I am using react+redux.
I have a modal form with data and images and on success I need to close the modal else display error returned from redux. In the dispatch function I have 1 more callback function to store images to S3. I am returning promise from the redux-thunk but I keep getting "TypeError: Cannot read property 'then' of undefined".
Component
handleSubmit = e => {
e.preventDefault();
if(this.isFieldEmpty()){
this.setState({ message: "All fields are mandatory with at least 1 pic" });
return;
} else {
this.setState({ message: "" });
}
const data = {
name: this.state.name,
description : this.state.description,
points : this.state.points,
attributes : this.state.attributes,
images : this.state.images,
created_by: localStorage.getItem('id'),
}
this.props.createItem(data).then(() => {
this.hideModal();
})
}
const mapDispatchToProps = dispatch => {
return {
createItem: data => {
return dispatch(createItem(data))
},
};
};
Action
const saveItemImages = (images,successcb, failurecb) => {
if(images.length > 0){
const formData = new FormData();
for(var x = 0; x<images.length; x++) {
formData.append('image', images[x])
}
const token = localStorage.getItem('token');
fetch(`${backendUrl}/upload/item-images/`, {
method: "POST",
headers: {
'Authorization': `Bearer ${token}`
},
credentials: 'include',
body: formData
})
.then(res => {
if(res.status === 200){
res.json().then(resData => {
successcb(resData.imagesUrl);
});
}else{
res.json().then(resData => {
failurecb(resData.message);
})
}
})
.catch(err => {
console.log(err);
});
} else {
successcb([]);
}
}
export const createItem = data => { return (dispatch) => {
saveItemImages(data.images, imagesUrl => {
data.images = imagesUrl;
return fetch(`${backendUrl}/admin/createItem`, {
method: 'POST',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'Authorization': `Bearer ${data.token}`
},
credentials: 'include',
body: JSON.stringify(data)
})
.then(res => {
if(res.status === 200){
res.json().then(resData => {
dispatch({
type: ADMIN_CREATE_ITEM_SUCCESS,
payload: resData
})
return true;
});
}else{
console.log("Save failed");
res.json().then(resData => {
dispatch({
type: ADMIN_CREATE_ITEM_FAILED,
payload: {
message: resData.message
}
})
})
}
})
.catch(err => {
dispatch({
type: ADMIN_CREATE_ITEM_FAILED,
payload: {
message: `Internal Error -- ${err}`
}
})
});
}, failedMessage => {
let payload = {responseMessage: failedMessage}
dispatch({
type: ADMIN_CREATE_ITEM_FAILED,
payload: payload
})
});
};
};
Thanks in advance for any help
You should return a Promise to create async flow for the action like this:
export const createItem = data => dispatch => new Promise((resolve, reject) => {
// do something was a success
resolve();
// do something was a fail
reject();
});
I need to test redux saga with jest. But it's not working. Here is what I tried
service.js
class Login {
login = async (user: any) => {
try {
const response = await axios({
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
url: `${API.BASE_URL}signIn`,
data: JSON.stringify({
LoginID: user.LoginID,
Password: user.Password,
}),
});
return Bluebird.resolve(response.data);
} catch (err) {
return Bluebird.reject(err);
}
}
}
export default new Login();
worker.js
export function* loginWorker(action) {
try {
const response = yield call(service.login, action.payload);
if (response.Success) {
yield put({type: actionTypes.LOGIN_SUCCESS_ACTION, response});
} else {
toastr.showToast(response.Message);
yield put({type: 'LOGIN_FAILURE', response});
}
} catch (err) {
// dispatch a failure action to the store with the error
yield put({type: 'LOGIN_FAILURE', err});
toastr.showToast(err);
}
}
test.js
describe('test login', () => {
const action = {
type: actionTypes.LOGIN_ACTION,
payload: user,
};
it('login', () => {
const gen = loginWorker(action);
expect(gen.next().value).toEqual(call(service.login, action.payload));
expect(gen.next().value).toEqual(put({type: actionTypes.LOGIN_SUCCESS_ACTION, response}));
});
});
But this function service.login not to be called and I get this error
Cannot read property 'Success' of undefined
Where is my wrong? Please help
I want to access this in the second then methode from the fetch.
But it keeps saying: TypeError: _this2.login is not a function
fbAuth() {
LoginManager.logInWithReadPermissions(['public_profile']).then(function(res) {
if (res.isCancelled) {
console.info('cancelled');
} else {
console.info('login success');
AccessToken.getCurrentAccessToken().then((data) => {
let req = new GraphRequest('/me', {
httpMethod: 'GET',
version: 'v2.5',
parameters: {
'fields': {
'string' : 'id,email,name'
}
}
}, (err, res) => {
const data = new FormData();
data.append('facebook_user_id', res.id);
data.append('email', res.email);
data.append('name', res.name);
fetch('xxxxxxxxxxx', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data,
})
.then((response) => response.json())
.then((responseJson) => {
this.login(responseJson);
})
.catch((error) => {
console.error(error);
alert('Something went wrong. Please try again, if the problem persists contact the #lacarte service desk');
});
});
new GraphRequestManager().addRequest(req).start();
});
}
}, function(error) {
console.error(error);
});
}
Assuming that this should be the same this that also has the fbAuth method, you should use an arrow function declaration for the first promise as well:
LoginManager.logInWithReadPermissions(['public_profile']).then(res => {
...
});
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.