Dispatch in Redux-Thunk - javascript

Uncaught (in promise) Error: Request failed with status code 400
I need to make a page request to the database for logging into the system, but I'm already too confused and don't know how to remove this error.
Before that there was the error "Actions must be plain objects. Use custom middleware for async actions."
After that I connected Redux-Thunk and the current error appeared.
Actions
export const auth = (email, password, isLogin) => {
return async(dispatch) => {
dispatch(authData())
let url = 'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIzaSyAU8gNE0fGG8z9zqUyh68Inw9_RzljhCCs'
if (isLogin) {
url = 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyAU8gNE0fGG8z9zqUyh68Inw9_RzljhCCs'
}
const response = await axios.post(url, authData)
console.log(response.data)
}
}
const authData = (email, password, returnSecureToken = true) => ({
type: 'LOGIN',
email,
password,
returnSecureToken
})
Component
loginHandler = () => {
this.props.auth(
this.props.AuthMail,
this.props.AuthPass,
true
)
}
registerHandler = () => {
this.props.auth(
this.props.AuthRegMail,
this.props.AuthRegPass,
false
)
}
const mapDispatchToProps = dispatch => {
return {
auth: (email, password, isLogin) => dispatch(auth(email, password, isLogin))
}
}

// You forgot to add the arguments to authData function
dispatch(authData())
// Here you are passing in a function as the second argument
const response = await axios.post(url, authData)
Should probably be something like this:
export const auth = (email, password, isLogin) => {
return async (dispatch) => {
const url = isLogin ? 'example.com/login' : 'example.com/signup';
const response = await axios.post(url, {
email,
password,
returnSecureToken: true,
});
console.log(response.data);
// Handle this action somewhere to store the signed in user data in redux
dispatch({
type: "LOGIN",
payload: response.data
})
}
}

Related

React app page routing issue post successful user login

I have a react app where I use the useContext and useReducer hooks for the login and storage. While the login part works, what I want achieve is to redirect user to a specific page post successful login. I am using react-router#6 and tried to use useNavigate() to navigate user to particular route though it doesn't seem to work.
const AuthService = async (dispatch) => {
const MSAL_CONFIG = {} // populate MSAL config for Microsoft Graph API for AD auth
const msalInstance = new msal.PublicClientApplication(MSAL_CONFIG);
try {
const loginResponse = await msalInstance.loginPopup(scopes);
var username = loginResponse.account.username;
var userid = username.slice(0, username.indexOf("#"));
const loginData = {
auth_token: loginResponse.idToken,
user: {
name: loginResponse.account.name,
id: userid,
email: username,
},
};
const sessionData = {
user_id: userid,
id_token: loginResponse.idToken,
access_token: loginResponse.accessToken,
}
sessionStorage.setItem("currentUser", JSON.stringify(loginData));
dispatch({ type: "LOGIN_SUCCESS", payload: loginData });
return { loginData: loginData, error: null };
// dispatch({ type: 'LOGIN_SUCCESS', payload: loginData });
//sessionStorage.setItem('currentUser', JSON.stringify(data));
} catch (err) {
console.log("+++ Login error : ", err);
dispatch({ type: "LOGIN_ERROR", error: err });
return { loginData: null, error: err };
}
};
In my header.jsx, I have below code to handle the login button. It makes a call to the above AuthService. The code post AuthService() call, i.e. the if block, doesn't take effect, so user never gets redirected to the dashboard page.
const handleLogin = async () => {
await AuthService(dispatch)
console.log("userDetails.token : " + userDetails.token)
if (Boolean(userDetails.token)) {
navigate("/dashboard");
}
};
If I'm correct in understanding that this AuthService function eventually resolves and that the dispatched LOGIN_SUCCESS action updates the userDetails variable that is selected from the auth context state, then I think you have all that you need and are close to a working solution. The issue is that the userDetails value from the render cycle the handleLogin is called in is closed over in callback scope, it will never be a different value. If the userDetails.token value is falsey when handleLogin is called, it will remain falsey in the entire callback scope.
The AuthService function appears to return the same loginData object that is passed in the dispatched LOGIN_SUCCESS action to the store. handleLogin should await this value and conditionally navigate.
const AuthService = async (dispatch) => {
...
try {
const { account, idToken } = await msalInstance.loginPopup(scopes);
const { name, username } = account;
const userid = username.slice(0, username.indexOf("#"));
const loginData = {
auth_token: idToken,
user: {
name,
id: userid,
email: username,
},
};
...
sessionStorage.setItem("currentUser", JSON.stringify(loginData));
dispatch({ type: "LOGIN_SUCCESS", payload: loginData });
return { loginData, error: null }; // <-- return value
} catch (error) {
dispatch({ type: "LOGIN_ERROR", error });
return { loginData: null, error }; // <-- return value
}
};
const handleLogin = async () => {
const { loginData } = await AuthService(dispatch);
if (loginData && loginData.auth_token) { // or loginData?.auth_token
navigate("/dashboard", { replace: true });
}
};

How to persist the Firebase Auth state between front-end and backend ? (Firebase Auth + React + Express)

I am trying to use Firebase Auth in backend, but I can't seem to be able to have the same Auth instance in the front-end as well.
The back-end:
'use strict';
import { firebaseAdmin, auth } from '../firebase.js';
import deleteCollection from '../helpers/deleteCollection.js';
import User from '../models/user.js';
import {
createUserWithEmailAndPassword,
updateProfile,
signInWithEmailAndPassword,
signOut,
setPersistence,
browserLocalPersistence,
} from 'firebase/auth';
const firestore = firebaseAdmin.firestore();
const register = async (req, res, next) => {
try {
// name, email, password
const { name, email, password, avatar } = req.body;
console.log('sent from frontend', { name, email, password });
// Check if email or password were sent
if (!email || !password) {
return res.status(422).json({
email: 'Email is required !',
password: 'Password is required !',
});
}
const usersCollection = firestore.collection('users');
// Reference to a QuerySnapshot whith all users that have the requested name
const userSnapshot = await usersCollection.where('name', '==', name).get();
// Check if user already exists:
if (!userSnapshot.empty) {
throw new Error('Username is taken !');
} else {
await setPersistence(auth, browserLocalPersistence);
// Firebase Auth Create User
await createUserWithEmailAndPassword(auth, email, password);
// User is signed in
const user = auth.currentUser;
if (user) {
await updateProfile(user, {
displayName: name,
});
const setUser = {
id: user.uid,
name: user.displayName,
avatar: avatar,
};
await usersCollection.doc(setUser.id).set(setUser);
res.status(201).send(setUser);
} else {
throw new Error('No user');
}
}
} catch (error) {
const errorCode = error.code;
const errorMessage = error.message;
res.status(400).send(errorMessage);
console.log(errorCode, errorMessage);
}
};
const login = async (req, res, next) => {
try {
const { email, password } = req.body;
await setPersistence(auth, browserLocalPersistence);
const userCred = await signInWithEmailAndPassword(auth, email, password);
const usersCollection = firestore.collection('users');
const userSnapshot = await usersCollection
.where('name', '==', userCred.user.displayName)
.get();
if (userSnapshot.empty) {
throw new Error('User does not exist !');
} else {
let user;
userSnapshot.forEach((doc) => (user = { ...doc.data() }));
res.status(200).send(user);
}
} catch (error) {
res.status(404).send(error.message);
console.log(error);
}
};
const logout = async (req, res, next) => {
try {
// const { name, email, password, avatar } = req.body;
await signOut(auth);
res.sendStatus(200);
} catch (error) {
const errorCode = error.code;
const errorMessage = error.message;
res.status(404).send(errorMessage);
console.log(error);
}
};
I call Register, Login and Logout using Redux thunkAPI:
const register = async (userData) => {
const response = await axios.post(API_REGISTER, userData, {
headers: {
// Overwrite Axios's automatically set Content-Type
'Content-Type': 'application/json',
},
});
if (response.data) {
// localStorage.setItem('user', JSON.stringify(response.data));
}
return response.data;
};
const login = async (userData) => {
const response = await axios.post(API_LOGIN, userData, {
headers: {
// Overwrite Axios's automatically set Content-Type
'Content-Type': 'application/json',
},
});
if (response.data) {
// localStorage.setItem('user', JSON.stringify(response.data));
}
return response.data;
};
const logout = async () => {
const response = await axios.get(`${API_LOGOUT}`);
if (response.data) {
localStorage.removeItem('user');
}
return response.data;
};
export const register = createAsyncThunk(
'user/register',
async (user, thunkAPI) => {
try {
return await userService.register(user);
} catch (error) {
return thunkAPI.rejectWithValue(error.response.data);
}
}
);
export const login = createAsyncThunk('user/login', async (user, thunkAPI) => {
try {
return await userService.login(user);
} catch (error) {
return thunkAPI.rejectWithValue(error.response.data);
}
});
export const logout = createAsyncThunk('user/logout', async (_, thunkAPI) => {
try {
return await userService.logout();
} catch (error) {
return thunkAPI.rejectWithValue(error.response.data);
}
});
I am able to Register a user, to login and to logout, but if I hit refresh I get logged out.
I am not able to persist the Firebase Auth state between front-end and backend.
This is the Private Route component
import { useSelector } from 'react-redux';
import { Navigate, useLocation } from 'react-router-dom';
import { auth } from '../../firebase';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useEffect } from 'react';
import { useState } from 'react';
let isAuth;
export default function PrivateRoute({ children }) {
const location = useLocation();
const [user, setUser] = useState();
// const isAuth = useSelector((state) => state.user.user);
// const [user, loading, error] = useAuthState(auth);
// useEffect(() => {
// if (loading) return;
// if (user) {
// isAuth = true;
// console.log(user);
// }
// }, [user, loading]);
useEffect(() => {
auth.onAuthStateChanged(setUser);
}, []);
return user ? (
children
) : (
<Navigate
replace={true}
to='/login'
state={{ from: `${location.pathname}${location.search}` }}
/>
);
}
As you can see from the commented code, I've tried multiple things before posting here but nothing works.
I don't want to move the Authentication logic from back-end to front-end.
I only want to have access to the same Auth state between back-end to front-end.
The approach you're using is not supported by Firebase. You're supposed to authenticate the user only on the frontend and never on the backend. The frontend SDK will persist a token that identifies the user. You then pass that token to the backend on each call and use it to verify the user so that the backend can decide if the operation they are trying to perform is allowed. This scheme is described in the documentation, and I strongly suggest reviewing that:
If your Firebase client app communicates with a custom backend server, you might need to identify the currently signed-in user on that server. To do so securely, after a successful sign-in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity and authenticity of the ID token and retrieve the uid from it. You can use the uid transmitted in this way to securely identify the currently signed-in user on your server.
Again, don't try to sign the user in on your backend using the frontend SDK - that is not supported and it does not scale. Only use the Firebase Admin SDK on the backend to validate the user ID tokens passed from the frontend.

How to adjust the order of events occurring in async Javascript?

I am working on authentication for my react native app.
The problem I am having is that the signInUser function seems to be executing in the wrong order. I want the signIn function to fully execute before moving on.
However, that doesn't seem to be happening as I am getting this response in my console, with the "undefined" coming from console.log(response) in the SignInScreen.
Here is the console:
undefined
16d0707a3508a9b43b8c36c8574ca73d8b4b26af
I have this function in the SignInScreen.js
import { signIn } from "../services/authService";
import { useAuthDispatch } from "../contexts/authContext";
const SignInScreen = ({ navigation }) => {
const dispatch = useAuthDispatch();
const [signInLoading, setSignInLoading] = useState(false);
const signInUser = async (values) => {
const { email, password } = values;
setSignInLoading(true);
signIn(email, password)
.then((response) => {
console.log(response);
dispatch({
type: "SIGN_IN",
token: response,
});
})
.catch((e) => {
console.log(e);
})
.finally(() => setSignInLoading(false));
};
And this is my authService.js:
import axios from "axios";
const signIn = async (email, password) => {
axios
.post("http://127.0.0.1:8000/rest-auth/login/", {
username: email,
email: email,
password: password,
})
.then((response) => {
console.log(response.data.key);
return response.data.key;
})
.catch((error) => {
return error;
});
};
How can I fix this?

React useReducer not updating state

I'm using useReducer to update the errorsState when user logged in and failed. I've read many solutions and it was said that dispatch is async and I know that so I put console.log inside the useEffect to see the errorsState change, but unfortunately it didn't changed. Here's my code
Login.jsx
export default function Login({ userProps }) {
//
// some variables and state
//
const { loading, user } = useLogin({ email: state.email }, state.submitted)
const [errors, dispatch] = useReducer(errorsReducer, errorsState)
useEffect(() => {
console.log("errors", errors) // it won't triggered because errors state didn't updating from UseLogin
}, [errors])
return content
}
Here is fetch function useLogin
AuthAction.js
export const useLogin = (data, submitted) => {
const [state, dispatch] = useReducer(userReducer, userState)
const [errors, errorsDispatch] = useReducer(errorsReducer, errorsState)
useEffect(() => {
if (!submitted) return
dispatch({
type: USER_ACTIONS.MAKE_REQUEST,
})
ticketApi.login(data).then(({ res, status }) => {
if (status !== "failed") {
// Save to local storage
const { token } = res
// set token to local storage
localStorage.setItem("jwtToken", token)
// Set token to Auth Header
setAuthToken(token)
// decode token to get user data with jwt-decode
const decoded = jwt_decode(token)
// set current user
return dispatch({
type: USER_ACTIONS.GET_USER,
payload: decoded,
})
}
dispatch({
type: USER_ACTIONS.END_REQUEST,
})
return errorsDispatch({
type: ERRORS_ACTIONS.GET_ERRORS,
payload: res.response.data,
})
})
}, [submitted])
return state
}
I've tried put console.log inside the ERRORS_ACTIONS.GET_ERRORS to see the response, and it was fine.
So where did i go wrong?
useReducer allows you to better manage complex states, it's not a state container, what you're doing there is to create 2 different states, one inside useLogin and the other in your Login component, return errors from your useLogin hook so the Login component can see it.
Login
export default function Login({ userProps }) {
//
// some variables and state
//
const { loading, user, errors } = useLogin({ email: state.email }, state.submitted)
useEffect(() => {
console.log("errors", errors)
}, [errors])
return content
}
useLogin
export const useLogin = (data, submitted) => {
const [state, dispatch] = useReducer(userReducer, userState)
const [errors, errorsDispatch] = useReducer(errorsReducer, errorsState)
useEffect(() => {
if (!submitted) return
dispatch({
type: USER_ACTIONS.MAKE_REQUEST,
})
ticketApi.login(data).then(({ res, status }) => {
if (status !== "failed") {
// Save to local storage
const { token } = res
// set token to local storage
localStorage.setItem("jwtToken", token)
// Set token to Auth Header
setAuthToken(token)
// decode token to get user data with jwt-decode
const decoded = jwt_decode(token)
// set current user
return dispatch({
type: USER_ACTIONS.GET_USER,
payload: decoded,
})
}
dispatch({
type: USER_ACTIONS.END_REQUEST,
})
return errorsDispatch({
type: ERRORS_ACTIONS.GET_ERRORS,
payload: res.response.data,
})
})
}, [submitted])
return { ...state, errors };
}

Redux thunk async action cannot access first parameter

I have an action that currently works fine using .then but when I try and convert it to async ... await it suddenly can't access the first parameter of the function. The second parameter still works fine though.
Current function that works fine:
export const signInUser = (email, password) => {
return (dispatch) => {
return firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
console.log('signed in')
const { uid, email } = firebase.auth().currentUser
dispatch({ type: 'SIGN_IN', uid, email })
return dispatch(fetchAllData())
})
.catch(error => {
throw (error)
})
}
};
New function that doesn't work because 'email' is undefined.
export const signInUser = (email, password) => {
return async (dispatch) => {
console.log('testing')
console.log(password)
console.log('testing', email, password)
await firebase.auth().signInWithEmailAndPassword(email, password)
console.log('signed in')
const { uid, email } = firebase.auth().currentUser
dispatch({ type: 'SIGN_IN', uid, email })
return dispatch(fetchAllData())
}
};
The first console log of console.log('testing') works fine and outputs the string 'testing'. The second console log of console.log(password) also works fine and prints the inputted password. But the third console log console.log('testing', email, password) doesn't get shown at all.
This is how the function is called:
Parent component passes it to child component:
submit={(email, password) => dispatch(signInUser(email, password))}
Child component calls it:
const submitForm = (event) => {
event.preventDefault();
if (validForm) {
setLoading(true)
submit(email, password)
.catch(err => {
setLoading(false)
console.log('catch:', err.code)
setError(err.code)
})
}
}
The output I am receiving is catch: undefined.
Also, if I change the function to this:
export const signInUser = (email, password) => {
const userEmail = email
return async (dispatch) => {
console.log('testing')
console.log(password)
console.log('testing', userEmail, password)
await firebase.auth().signInWithEmailAndPassword(userEmail, password)
console.log('signed in')
const { uid, email } = firebase.auth().currentUser
dispatch({ type: 'SIGN_IN', uid, email })
return dispatch(fetchAllData())
}
};
Then it works fine. But I don't know why I would need to change it like this?
Thanks
Ok so the reason it wasn't working was because on the next line I was declaring email like this const { uid, email } = firebase.auth().currentUser
which was overriding the function parameter of email and therefore giving me the error that i was using email before it was declared.

Categories