I am trying to add authentication to a react website using firebase
I currently have this createasyncthunk
export const signin = createAsyncThunk<
// Return type of the payload creator
User | void,
// First argument to the payload creator
userData,
// Types for ThunkAPI
{
rejectValue: errorInterface;
}
>('authentication/signin', (user, thunkApi) => {
const { email, password } = user;
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
return user;
})
.catch((error) => {
console.log(error.message, error.code)
const errorMessage: errorInterface = { message: error.code };
return thunkApi.rejectWithValue(errorMessage);
});
});
the user type is imported from fire base
the userData is the following interface to represent credentials used to log in
interface userData {
email: string;
password: string;
}
the error interface is the following
interface errorInterface {
message: string;
}
the authentication slice is this
export const authSlice = createSlice({
name: 'authentication',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(signin.pending, (state) => {
state.status = 'loading';
state.isAuth = false;
})
.addCase(signin.fulfilled, (state, action) => {
state.status = 'idle';
state.isAuth = true;
if (action.payload) {
state.user = action.payload;
}
})
.addCase(signin.rejected, (state, action) => {
state.status = 'failed';
state.isAuth = false;
if (action.payload) {
state.message = action.payload.message;
}
})
},
});
The problem is that signin.rejected is never trigered. Even when the catch block runs, rejectwithvalue is not updating the payload. I know the catch block is running because I can see the error in the console. Please help me out, thanks in advance
Related
I am creating a registration functionality with redux. I have setup the store and reducer and the function register().
However, when I call the endpoint for the registration api it just doesnt register the user and from the redux actions file i get undefined for all of my data.
whenever i go to my redux dev tools i see that it says "Name is Required", "Username is Required", which are backend validation i have set up from my nodejs api, meaning that no values are reaching the api and everything i enter is undefined. Why is that?
I am new to redux so i don't know how to debug the issue or understand what is going wrong.
Here is my code:
actions/auth.js file:
export const register = ({ name, uniID, username, email, phoneNumber, uniIDImage, password }) => async dispatch => {
console.log(name, email, phoneNumber, uniIDImage, password);//logs undefined
const body = JSON.stringify({ name, uniID, username, email, phoneNumber, uniIDImage, password });
const emailBody = JSON.stringify({ name, email });
try {
const res = await axios.post('/register', body);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data
});
} catch (err) {
dispatch({
type:REGISTER_FAIL,
});
}
}
reducers/auth.js:
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
} from "../actions/types";
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: null,
loading: true,
user: null
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case REGISTER_SUCCESS:
localStorage.setItem('token', payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false
}
case REGISTER_FAIL:
localStorage.removeItem('token');
return {
...state,
token: null,
isAuthenticated: false,
loading: false
}
default:
return state;
}
}
Register.js:
import React, {useEffect, useReducer, useState} from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import axios from "axios"
import { register } from '../../actions/auth';
const Register = ({ register }) => {
const [data, setData] = useState({
name: '',
uniID: '',
username: '',
email: '',
phoneNumber: '',
password: '',
});
const [uniIDImage, setUniIDImage] = useState([]);
const [success, setSuccess] = useState(false);
const handleChange = (e) => {
setData({ ...data, [e.target.name]: e.target.value });
}
const handleImage = (e) => {
e.persist()
setUniIDImage({ pic: e.target.files[0] });
}
const onRegister = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("name", data.name);
formData.append("uniID", data.uniID);
formData.append("username", data.username);
formData.append("email", data.email);
formData.append("phoneNumber", data.phoneNumber);
formData.append("uniIDImage", uniIDImage.pic);
formData.append("password", data.password);
register({formData});
}
Register.propTypes = {
register: PropTypes.func.isRequired,
};
export default connect(null, {register})(Register);
you are passing "formData" type
// this is correct
register(formData);
but you are destructuring like a regular object
export const register = ({ name, uniID, username, email, phoneNumber, uniIDImage, password }) => async dispatch => {
If you check this mdn formData
const formData = new FormData();
formData.append('key1', 'value1');
formData.append('key2', 'value2');
// Display the values
for (const value of formData.values()) {
console.log(value);
}
when you received the formData inside register convert it to an object
export const register =(formData) => async(dispatch)=> {
let jsonObject = {};
for (let key of FormData.keys()) {
jsonObject[key] = formData.get(key);
}
// in axios post jsonObject
const res = await axios.post('/register', jsonObject);
};
This is the async thunk function in my slice, I'm using axios (http is coming from another file defined as axios.create())
export const loginUser = createAsyncThunk("auth/fetchUsers", async (data) => {
const response = await http.post("/login", data);
return response.data;
});
In above data is an object passed through dispatch method (data contains email and password).
This is my initial state,
const initialState = {
loading: false,
user: [],
error: [],
},
This is my reducer, I use extra reducers So I can stage the above loginUser into stages as pending,fullfilled,rejected
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
},
extraReducers: (builder) => {
builder.addCase(loginUser.pending, (state) => {
state.apiHandler.loading = true;
});
builder.addCase(loginUser.fulfilled, (state, action) => {
state.apiHandler.loading = false;
state.apiHandler.user = action.payload;
state.apiHandler.error = [];
});
builder.addCase(loginUser.rejected, (state, action) => {
state.apiHandler.loading = false;
state.apiHandler.user = [];
state.apiHandler.error = action.error.message;
});
},
});
I'm trying to get the exact error messages returning from Laravel. But I'm getting the message as Request failed with status code 422 in the rejected cycle.With postman I'm retrieving the exact error,
{
"message": "Incorrect Credentials"
}
This is a potion of my Laravel code, (So if something is not fullfilled it should return errors),
$request->validate([
'email' => 'required | exists:users',
'password' => 'required'
]);
My question is: How to retrieve the exact error messages with asyncThunk reject method without retrieving Request failed with status code 422 as the error.
I don't know about your project structure. However, you can actually get the Laravel errors, by waiting for a json response and accessing response's .errors .
For Example:
if (response. Status === 422) {
const validation = await response.json();
this.message= validation.errors
this.error= true;
}
According to your project structure, place this logic in your responses, anywhere suitable.
What I see here is you return response.data anyways, while laravel errors reside in response.errors object.
I hope it helps.
I attached try catch and passed the 2nd argument as {rejectWithValue} for the createAsyncThunk, and catching the errors with action.payload in case.rejected ,
export const loginUser = createAsyncThunk(
"auth/fetchUsers",
async (payload, { rejectWithValue }) => {
try {
const { data } = await http.post("/login", payload);
return data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
extraReducers: (builder) => {
...
builder.addCase(loginUser.rejected, (state, action) => {
state.loading = false;
state.user = [];
state.error = action.payload;
});
},
When I send request I got my data back in json and I can see it when console.log().
But when I try to change my state, it's not changing. Can you guys please help me to understand why? Don't judge too hard I am still learning. Thank you
Here is my code
export const login = createAsyncThunk(
'auth/login',
async (user) => {
try {
const response = await loginUser(user);
console.log(response.data) /// data present
return response.data
} catch (error) {
console.log(error)
}
}
)
export const authSlice = createSlice({
name: 'auth',
initialState: { user: {}, status: '', message: '', success: false, error: '' },
reducers: {
[login.pending]: (state, action) => (
state.status = 'loading'
),
[login.fulfilled]: (state, { payload }) => {
state.user = payload.user
state.status = 'success'
state.message = payload.message
state.success = true
state.error = ''
},
[login.rejected]: (state, { payload }) => {
state.user = payload
state.status = 'failed'
state.success = false
state.error = payload
}
}
})
You need use the extraReducers with builder, something like that :
reducers: {
//something, we don't care it
},
extraReducers: (builder) => {
builder.addCase(login.pending, (state) => {
state.status = what you want
});
builder.addCase(login.fulfilled, (state) => {
blabla
});
... and again and again
},
see https://redux-toolkit.js.org/api/createAsyncThunk (all is on the doc, no better source for RDK)
usersSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { API } from "../axios/index";
export const signUp = ...
export const logOut = ...
export const signIn = createAsyncThunk("users/signin", async (params) => {
try {
const { loginData, history } = params;
const { data } = await API.post("users/signin", loginData);
history.push("/");
return data;
} catch (error) {
console.log(error);
}
});
const initialState = {
usersInfo: {},
status: "idle",
error: null,
existEmail: false,
};
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {
handleExistEmail: (state, action) => {
state.existEmail = action.payload;
},
},
extraReducers: {
...
[signIn.fulfilled]: (state, action) => {
console.log("here is your data : ", action.payload);
state.status = "succeeded";
if (action.payload) {
localStorage.setItem("user", JSON.stringify(action.payload));
}
},
},
});
export default usersSlice.reducer;
export const { handleExistEmail } = usersSlice.actions;
userRouter.js
const isPasswordCorrent = await bcrypt.compare(password, user.password);
if (!isPasswordCorrent) {
return res
.status(404)
.json({ message: "Password dont match" });
}
Hi all.When password and re-passwordn dont match i want to backend send me status(404) and json({ message: "Password dont match" }) values and i want to catch these values in [signIn.fulfilled] but action.payload send me undefined.But if i do return res.json({ message: "Password dont match" }) instead of return.status(404).json({message: "Password dont match"}) this time i cant catch json({message: "Password dont match"}) from [signIn.fulfilled].Why i have to delete .status(404) part to dont get undefined ?
This is how the createAsyncThunk works. This wrapper itself is a try/catch block, so doesn't make sense to use in this action creator function. If a promise is rejected in this creator function body, then your action returns a rejected sub-action in the store. So you have to listen to this action in the reducer. Or if you really want to use a try/catch block, then in the catch block throw the error. A little example usage:
export const exampleAsyncAction = createAsyncThunk(
ACTION_TYPE,
async (parameter) => {
const result = await apicall(parameter);
return result.doSomeLogic();
}
);
const reducer = createReducer(
...,
[exampleAsyncAction.pending]: (state) => {
state.loading = true;
state.error = null;
},
[exampleAsyncAction.fulfilled]: (state, {payload}) => {
state.result = payload;
state.loading = false;
},
[exampleAsyncAction.rejected]: (state, {error}) => {
state.error = error;
state.loading = false;
},
)
can somebody explain why this code dispatching 'actions.loginSuccess' when i get 401 error from server ?
isn't it should go to 'catch' part of axios request ?
Before i did it without redux toolkit features
const login = ({username, password}) => async dispatch => {
await axios.post(`${API_URL}/token/`, {username, password})
.then(response => {
dispatch(actions.loginSuccess({ client_id: response?.data.client_id }))
history.push('/')
})
.catch(e => {
dispatch(actions.loginError({ error: String(e) }))
})
}
//actions.js
const login = createAction('#USER/login')
const loginSuccess = createAction('#USER/login-success')
const loginError = createAction('#USER/login-error')
export const actions = {
login,
loginSuccess,
loginError
}
//reducers.js
export const userReducer = createReducer(initialState, builder => {
builder.addCase(actions.login, draft => {
draft.loading = true
})
builder.addCase(actions.loginSuccess, (draft, action) => {
draft.loading = false
draft.isLoggedIn = true
draft.data = { ...draft.data, client_id : action.client_id}
})
builder.addCase(actions.loginError, (draft, action) => {
draft.loading = false
draft.error = action.payload.error
draft.isLoggedIn = false
draft.isSignedup = false
})
}
can somebody explain why this code dispatching 'actions.loginSuccess'
when i get 401 error from server ? isn't it should go to 'catch' part
of axios request ?
// there's a difference beetween HTTP Status Code and Server Response Body Code.
// if HTTP status code is not 200, it should dispatched loginError()
// if HTTP status code is 200, and theres a response body JSON
// e.g
const resp = {
statusCode: 401,
message: 'unauthorized',
}
// You must make if conditions to handle that error code
Here's redux-toolkit version of your code to handle either HTTP status code 401, or body response code
// import axios & history
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
data: {},
loading: false,
isLoggedIn: false,
isSignedup: false,
};
// Reducers
const userSlice = createSlice({
name: '#USER',
initialState: initialState,
reducers: {
loginStart(state) {
state.loading = true;
},
loginSuccess(state, action) {
state.data = {
...state.data,
client_id: action.payload.client_id
};
state.loading = false;
state.isLoggedIn = true;
},
loginError(state, action) {
state.loading = false;
state.error = action.payload.error;
state.isLoggedIn = false;
state.isSignedup = false;
},
},
});
// actions
export const { loginStart, loginSuccess, loginError } = userSlice.actions;
export default userSlice.reducer;
export const login = ({ username, password }) => async (dispatch) => {
dispatch(loginStart());
try {
const response = await axios.post(`${API_URL}/token/`, {
username,
password,
});
if(response && response.statusCode !== 200){
return dispatch(loginError({ error: response.message }));
}
dispatch(loginSuccess({ client_id: response?.data.client_id }));
history.push('/');
} catch (e) {
dispatch(loginError({ error: String(e) }));
}
};
don't forget to add userSlice into configureStore()
const reducer = {
"#USER": userReducers, //got from export default userSlice.reducer
};
export default configureStore({
reducer,
middleware,
devTools: process.env.NODE_ENV !== 'production',
});