I got the following error and I'm looking for the solution.
Possible Unhandled Promise Rejection (id: 0)
TypeError: undefined is not an object (evaluating 'e.type')
I found a lot of questions about this problem, almost solutions are inserting throw error into catch statement.
So I tried it, but the error's not gone.
What is wrong with my code?
Technologies
react-native
redux
react-navigation
This is my code.
Action
:
export function requestSignIn(email, password) {
return function (dispatch) {
// change loading status
dispatch(startedRequest());
if (email && password) {
firebase.auth().signInWithEmailAndPassword(email, password)
.then(response => {
if (response) {
// save email and password in local secure storage.
SecureStorage.setItem('email', email);
SecureStorage.setItem('password', password);
// save uid into store
dispatch(requestSignInSuccess(response.user.uid));
dispatch(NavigationService.navigate('Home', { uid: response.user.uid }));
} else {
dispatch(requestSignInFailure('network error'));
}
})
.catch(error => {
// I tried inserting 'throw error' here.
// throw error;
switch (error.code) {
case 'auth/user-not-found':
dispatch(requestSignInFailure('user not found'));
break;
case 'auth/invalid-email':
dispatch(requestSignInFailure('invalid email'));
break;
default:
dispatch(requestSignInFailure('something went wrong'));
}
})
} else {
dispatch(requestSignInFailure('error message from else statement'))
}
}
}
export function requestSignInSuccess(uid) {
return {
type: REQUEST_SIGN_IN_SUCCESS,
payload: {
uid: uid
}
}
}
export function requestSignInFailure(errorMessage) {
return {
type: REQUEST_SIGN_IN_FAILURE,
payload: {
errorMessage: errorMessage
}
}
}
The component calls the above Action
:
class SignIn extends Component {
async handleSignIn() {
const { requestSignIn } = this.props;
const { email, password } = this.props.auth;
Keyboard.dismiss();
requestSignIn(email, password);
}
:
Addition
I removed the statement dispatch(NavigationService.navigate('Home', { uid: response.user.uid }));, the error won't appear.
The cause of the problem is the screen has changed while the function is running?
If it's true, how do I execute dispatch(NavigationService.navigate('Home', { uid: response.user.uid }));
after dispatch(requestSignInSuccess(response.user.uid)); finished?
Related
Ok So i am trying to display my backend error messages in the front end, so I have it setup to send the response with the error code and a message and then in my action I am setting a state in my React component which I will then use to display the error message, so far I can get to display the error code but that is no use to most users so I would like to access the message I send with the code! So I want it to say user already exists or passwords do not match rather than Error: Request failed with status code 400
my action
export const signup = (form, router, setError) => async (dispatch) => {
const changeError = (error) => {
setError(error);
};
try {
const { data } = await api.signup(form);
dispatch({ type: AUTH, data });
router.push("/");
} catch (error) {
console.log(error);
changeError(error);
}
};
my node signup
export const signup = async (req, res) => {
const { email, password, confirmPassword, firstName, lastName } = req.body;
try {
const existingUser = await user.findOne({ email });
if (existingUser)
return res.status(400).json({ message: "User already exists." });
if (password != confirmPassword)
return res.status(400).json({ message: "Passwords do not match." });
const hashedPassword = await bcrypt.hash(password, 12);
const result = await user.create({
email,
password: hashedPassword,
name: `${firstName} ${lastName}`,
});
const token = jwt.sign(
{ email: result.email, id: result._id },
process.env.JWT_KEY,
{
expiresIn: "1h",
}
);
res.status(200).json({ result, token });
} catch (error) {
res.status(500).json({ message: "Something went wrong." });
}
};
After little search on Google, if you are using Axios as your api, the path to the error message is:
error.response.data.message
else, have you tried somthing like this?
error.data.message
or
error.message
as Guy said, slightly before I found the answer myself I set the error to error.response.data.message
so now I can set my error in the front end to display the message
and yea sorry was using axios, I'll know better for next time to mention that!
export const signup = (form, router, setError) => async (dispatch) => {
const changeError = (error) => {
setError(error);
};
try {
const { data } = await api.signup(form);
dispatch({ type: AUTH, data });
router.push("/");
} catch (error) {
console.log(error);
changeError(error.response.data.message);
}
};
Here i have my component code for SignIng Up user and check for Error. At first error is null.
let error = useSelector((state) => state.authReducer.error);
const checkErrorLoading = () => {
console.log("If error found"); //At first it gives null, but on backend there is error
toast.error(error);
console.log(loading, error);
};
const handleSubmit = async (e) => {
if (isSignup) {
dispatch(signup(form, history));
checkErrorLoading();
} else {
dispatch(signin(form, history));
checkErrorLoading();
}
};
Now at my singupForm, i provide wrong input or wrong data. The backend gives me error that is completely fine.
ISSUE => But when i click on Login button. At first attempt it does not provide any error message. After second attempt it works fine, but not at first attempt. At first attempt it gives me Error value NULL while there is still an error
Here is my action.
export const signup = (formData, history) => async (dispatch) => {
try {
const res = await api.signUp(formData);
dispatch({ type: authConstants.AUTH_REQUEST });
if (res.status === 200) {
const { data } = res;
console.log(data);
dispatch({
type: authConstants.AUTH_SUCCESS,
payload: data,
});
}
console.log(res.status);
history.push("/");
} catch (error) {
console.log(error.response);
dispatch({
type: authConstants.AUTH_FAILURE,
payload: error.response.data.error,
});
}
};
and than reducer.
const initialState = {
authData: null,
error: null,
loading: false,
};
const authReducer = (state = initialState, action) => {
switch (action.type) {
case authConstants.AUTH_REQUEST:
return { ...state, loading: true, error: null };
case authConstants.AUTH_SUCCESS:
localStorage.setItem("profile", JSON.stringify({ ...action?.payload }));
return { ...state, authData: action?.data, loading: false, error: null };
case authConstants.AUTH_FAILURE:
console.log(action.payload);
return { ...state, loading: false, error: action.payload };
}
You should use useEffect instead of local function (checkErrorLoading ) for such cases:
useEffect(() => {
console.log("If error found");
toast.error(error);
console.log(loading, error);
},[error]);
Currently what you doing is creating local function that closures error variable, which initially is null + state is updated asynchronously, so you cannot execute function right after dispatching (even if variable wouldn't be closured, you will not have fresh state there)
I set up an axios response interceptor for my react app. It works fine for catching most errors but one thing i am having trouble with is if the response is a 401 aka the user is not authed, the interceptor sends the user back to the login page. Now this works but the logic inside the .then from the original request still runs. This causes a type error as in the .then logic i am setting a state with the response data. Here is my current attempt at implementing a axios cancel token that is not working. See the code below. What am i missing here? What is the best way to achieve this with out having to add If/Else logic to every axios request to check if "data" is there or is the response is a 401, 200 ...?
AxiosInterceptor.js
...
export default withRouter({
useSetupInterceptors: (history) => {
axios.interceptors.response.use(response => {
return response;
}, error => {
try {
if (error.response.status === 401) {
history.push("/login");
Swal.fire({
title: '401 - Authorization Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Close',
})
throw new axios.Cancel('Operation canceled');
}
return Promise.reject(error);
} catch (error) {
console.log(error)
}
});
},
});
UserPage.js
...
function userPage() {
var [pageData, setPageData] = useState('');
var classes = useStyles();
useEffect(() => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const loadData = () => {
try {
axios.post('/api/getUserData', { cancelToken: source.token })
.catch(function (error) {
source.cancel();
})
.then(res => {
const data = res.data;
setPageData(data);
})
} catch (error) {
if (axios.isCancel(error)) {
console.log('Op Cancel')
} else {
throw error;
}
}
};
loadData();
return () => {
source.cancel();
};
}, []);
return (
...
);
}
...
The error i get:
Unhandled Rejection (TypeError): Cannot read property 'data' of undefined
PROGRESS UPDATE:
I added some logic to my back-end that if the login is successful,
i pass the expiration time of the JWT token back to my front end.
Then push that expiration epoch to my redux store.
On every request, in my 'AxiosInterceptor.js' file below, before returning a config back, i validate the exp value set in redux.
Now this works fine on initial login, but once the token has expired and you receive the popup from 'Swal.fire' and click 'return' it does two things:
calls logOut action and returns all values to initial state. (This works fine. I validated with redux-devtools-extension)
Now i can log back in. Everything starts to load fine but then i get the 'Swal.fire' dialog to return back to login page. When logging the user.exp and date.now to console i see some strange behavior(see comments):
// from redux-logger
action SET_EXP # 20:05:42.721
redux-logger.js:1 prev state {user: {…}, _persist: {…}}
redux-logger.js:1 action {type: "SET_EXP", payload: 1585267561036}
USEREXP 1585267561036 // this is the new EXP time set in redux, received from back end on login
AxiosInterceptors.js:17 Current Status = 1585267561036 false // first two axios calls on main page validate and indicate not expired
AxiosInterceptors.js:17 Current Status = 1585267561036 false
AxiosInterceptors.js:17 Current Status = 1585267495132 true // this is the value of the previos exp value that was set
AxiosInterceptors.js:17 Current Status = 1585267495132 true
AxiosInterceptors.js:17 Current Status = 1585267352424 true // this is the value that was set two login times ago
AxiosInterceptors.js:17 Current Status = 1585267352424 true
How is this possible? I verified with redux-devtools that once i am
returned back to the login page, it is indeed empty. It appears the value in > redux-store is being rolled back to old values? I am using chrome Version
74.0.3729.131 (Official Build) (64-bit). I have tried with incognito mode and clearing cache and cookies.
New AxiosInterceptor.js ...
export default withRouter({
useSetupInterceptors: (history) => {
let user = useSelector(state => state.user)
axios.interceptors.request.use(config => {
const { onLogo } = useLogout(history);
console.log("Current Status = ", user.exp, Date.now() > user.exp)
if (Date.now() > user.exp) {
Swal.fire({
title: '401 - Auth Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Return',
}).then((result) => {
onLogo();
})
return {
...config,
cancelToken: new CancelToken((cancel) => cancel('Cancel')) // Add cancel token to config to cancel request if redux-store expire value is exceeded
};
} else {
return config;
}
}, error => { console.log(error)});
axios.interceptors.response.use(response => {
return response;
}, error => {
try {
if (axios.isCancel(error)) { // check if canceled
return new Promise(() => {}); // return new promise to stop axios from proceeding to the .then
}
if (error.response.status === 401) {
history.push("/login");
Swal.fire({
title: '401 - Auth Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Close',
})
throw new axios.Cancel('Operation canceled');
}
return Promise.reject(error);
} catch (error) {
console.log(error)
}
});
},
});
function useLogo(history) {
const dispatch = useDispatch()
return {
onLogo() {
dispatch(allActs.userActs.logOut())
history.push("/login");
},
}
}
I tracked down the issue to the hook "useSelector" within react-redux. It seems this is some how returning cached data, after it already returned correct data. I am using version 7.2 at his time but i confirmed it also on v7.1. I have not tested on any other versions. I solved this by pulling the data from redux-persist Storage(localStorage) in the getExpire() function below. Not the most elegant solution but my application is now working as it should be.
export default withRouter({
useSetupInterceptors: (history) => {
const { onLogout } = useLogout(history);
const CancelToken = axios.CancelToken;
const { onExp } = useExp();
axios.interceptors.request.use((config) => {
const testexp = onExp();
if (testexp) {
Swal.fire({
title: '401 - Authorization Failed',
text: '',
icon: 'warning',
showCancelButton: false,
confirmButtonText: 'Return',
}).then((result) => {
onLogout();
})
return {
...config,
cancelToken: new CancelToken((cancel) => cancel('Cancel repeated request'))
};
} else {
return config;
}
}, error => { console.log(error) });
axios.interceptors.response.use(response => {
return response;
}, error => {
try {
if (axios.isCancel(error)) {
return new Promise(() => { });
}
return Promise.reject(error);
} catch (error) {
console.log(error)
}
});
},
});
function getExpire () {
var localStore = localStorage.getItem("persist:root")
if (localStore) {
let store = JSON.parse(localStore)
return JSON.parse(store.exp)
}
return 0
}
function useExp() {
// const currentExp = useSelector(state => state.exp)
return {
onExp() {
if (Date.now() > getExpire().exp) {
return true
} else { return false }
},
}
}
function useLogout(history) {
const dispatch = useDispatch()
return {
onLogout() {
dispatch(allActions.expAction.setLogout())
history.push("/login");
},
}
}
For async requests, I'm using redux-saga.
In my component, I call an action to recover the user password, it its working but I need a way to know, in my component, that the action I dispatched was successfully executed, like this:
success below is returning:
payload: {email: "test#mail.com"}
type: "#user/RecoverUserPasswordRequest"
__proto__: Object
My component:
async function onSubmit(data) {
const success = await dispatch(recoverUserPasswordRequestAction(data.email))
if (success) {
// do something
}
}
My actions.js
export function recoverUserPasswordRequest(email) {
return {
type: actions.RECOVER_USER_PASSWORD_REQUEST,
payload: { email },
}
}
export function recoverUserPasswordSuccess(email) {
return {
type: actions.RECOVER_USER_PASSWORD_SUCCESS,
payload: { email },
}
}
export function recoverUserPasswordFailure() {
return {
type: actions.RECOVER_USER_PASSWORD_FAILURE,
}
}
My sagas.js
export function* recoverUserPassword({ payload }) {
const { email } = payload
try {
const response = yield call(api.patch, 'user/forgot-password', {
email
})
// response here if success is only a code 204
console.log('response', response)
yield put(recoverUserPasswordSuccess(email))
} catch (err) {
toast.error('User doesnt exists');
yield put(recoverUserPasswordFailure())
}
}
export default all([
takeLatest(RECOVER_USER_PASSWORD_REQUEST, recoverUserPassword),
])
In my reducer.js I dont have nothing related to recover the user's password, like a RECOVER_USER_PASSWORD_SUCCESS because like I said, the api response from my saga is only a code 204 with no informations
You should treat this as a state change in your application.
Add a reducer that receives these actions RECOVER_USER_PASSWORD_SUCCESS or RECOVER_USER_PASSWORD_FAILURE, then updates the store with information about request status. For example:
const initialState = {
email: null,
status: null,
}
const recoverPasswordReducer = (state=initialState, action) => {
//...
if (action.type === actions.RECOVER_USER_PASSWORD_SUCCESS) {
return {...initialState, status: True }
}
if (action.type === actions.RECOVER_USER_PASSWORD_SUCCESS) {
return {...initialState, status: False }
}
return state;
}
You can later have status as one of the fields selected in mapStateToProps when connect the component that needs to know about the status of the operation to the store.
function mapStateToProps(state) {
return {
/* ... other fields needed from state */
status: state.status
}
}
export connect(mapStateToProps)(ComponentNeedsToKnow)
I create login form with reactjs app and also i create api. For backend i want to create function that only role admin can logged in inside coz for now every user can login to backend.
i have function for login, this is my api function:
const login = async (req, res, next) => {
const { body } = req;
const { username, password } = body;
...
} else {
UserModel.find({
username: username
}, (err, users) => {
if (err) {
res.send({
success: false,
message: 'Error: Server error.'
})
} else if (users.length != 1) {
res.send({
success: false,
message: 'Error: User not exist!'
})
} else {
const user = users[0]
const roleId = user.role
const role = await checkUserRole(roleId)
console.log(role)
if (!user.validPassword(password)) {
res.send({
success: false,
message: 'Error: Password not match.'
})
} else {
res.send({
success: true,
message: 'Login success'
})
}
}
})
}
}
and i want to check if role user is admin, so i create function before
const checkUserRole = async (roleId) => {
return RoleModel.findById(roleId).exec((err, role) => {
if (err) {
return message = 'Server error.'
} else if (role.roleName != 'admin') {
return message = 'You are not admin. Please use admin credential!'
}
})
}
but the result when i console.log(role) is undefined.
How to get result from checkUserRole? Or did i missed something?
The code below should work:
const checkUserRole = async (roleId) => {
return await RoleModel.findById(roleId,(err, role) => {
if (err) {
return message = 'Server error.'
} else if (role.roleName != 'admin') {
return message = 'You are not admin. Please use admin credential!'
}
})
}
Your original code is not working because you are wrapping the promise returned by exec into the callback. The above code returns the query. You can also try another option using only exec() without the callback and handle the errors with a try catch.