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',
});
Related
Hello having the following error
Journal.jsx:20 Uncaught TypeError: Cannot destructure property 'feeder' of '(0 , react_redux__WEBPACK_IMPORTED_MODULE_1__.useSelector)(...)' as it is undefined
I can by pass the error if i do "|| {}" on the state.feeder line in the first file
but the only thing that allows me to do is by pass the fact that its undefined.
i need to find a way to define the feeder state so i can display the items i am getting from my api.
speaking of my api when i bypass the the error by using the || i can see in redux that i am able to get the items in the state. which is confusing me.
the following code is:
Journal.js
const {user} = useSelector((state) => state.auth)
const { feeder, isLoading, isError, message } = useSelector(
(state) => state.feeder
)
feederSlice.js
feeder: [],
isError: false,
isSuccess: false,
isLoading: false,
message:''
}
export const createFeeder = createAsyncThunk('feeder/create', async (feederData, thunkAPI) => {
try{
const token = thunkAPI.getState().auth.user.token
return await feederService.createFeeder(feederData, token)
} catch(error){
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
})
export const getFeeder = createAsyncThunk('feeder/getAll', async (_, thunkAPI) => {
try{
const token = thunkAPI.getState().auth.user.token
return await feederService.getFeeder(token)
} catch(error){
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString()
return thunkAPI.rejectWithValue(message)
}
})
export const feederSlice = createSlice({
name: 'feeder',
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers:(builder) => {
builder
.addCase(createFeeder.pending, (state) => {
state.isLoading = true
})
.addCase(createFeeder.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.feeder.push(action.payload)
console.log(state.feeder)
})
.addCase(createFeeder.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
.addCase(getFeeder.pending, (state) => {
state.isLoading = true
console.log('getfeeder loading',state.feeder)
})
.addCase(getFeeder.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.feeder = action.payload
console.log('getfeeder success',state.feeder)
})
.addCase(getFeeder.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
console.log('getfeeder rejected',state.message)
})
}
})
export const {reset} = feederSlice.actions
export default feederSlice.reducer
----------------
feederService.js
const API_URL ='/api/feeder'
const createFeeder =async(feederData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`
}
}
const response = await axios.post(API_URL, feederData, config)
return response.data
}
const getFeeder = async (token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
}
const response = await axios.get(API_URL, config)
return response.data
}
const feederService = {
createFeeder,
getFeeder
}
export default feederService
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)
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
So I have created these contexts to handle logging users in and retrieving the logged user to any component that might need it.
Here they are:
context.js
import React, { useReducer } from "react";
import { AuthReducer, initialState } from "./reducers";
const AuthStateContext = React.createContext();
const AuthDispatchContext = React.createContext();
export function useAuthState() {
const context = React.useContext(AuthStateContext);
if (context === undefined) {
throw new Error("useAuthState must be used within a AuthProvider");
}
return context;
}
export function useAuthDispatch() {
const context = React.useContext(AuthDispatchContext);
if (context === undefined) {
throw new Error("useAuthDispatch must be used within a AuthProvider");
}
return context;
}
export const AuthProvider = ({ children }) => {
const [user, dispatch] = useReducer(AuthReducer, initialState);
return (
<AuthStateContext.Provider value={user}>
<AuthDispatchContext.Provider value={dispatch}>
{children}
</AuthDispatchContext.Provider>
</AuthStateContext.Provider>
);
}
reducers.js
let user = localStorage.getItem("currentUser")
? JSON.parse(localStorage.getItem("currentUser")).user
: "";
let token = localStorage.getItem("currentUser")
? JSON.parse(localStorage.getItem("currentUser")).token
: "";
export const initialState = {
userDetails: user || "",
token: token || "",
loading: false,
errorMessage: null,
};
export const AuthReducer = (initialState, action) => {
switch (action.type) {
case "REQUEST_LOGIN":
return {
...initialState,
loading: true,
};
case "LOGIN_SUCCESS":
return {
...initialState,
userDetails: action.payload.user,
token: action.payload.token,
loading: false,
};
case "LOGOUT":
return {
...initialState,
userDetails: "",
token: "",
};
case "LOGIN_ERROR":
return {
...initialState,
loading: false,
errorMessage: action.error,
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
};
actions.js
const ROOT_URL = process.env.REACT_APP_API_HOST_URL;
export async function loginUser(dispatch, loginPayload) {
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(loginPayload),
};
try {
dispatch({ type: "REQUEST_LOGIN" });
let response = await fetch(`${ROOT_URL}/auth/login`, requestOptions);
let data = await response.json();
if (data.user) {
dispatch({ type: "LOGIN_SUCCESS", payload: data });
localStorage.setItem("currentUser", JSON.stringify(data));
return data;
}
dispatch({ type: "LOGIN_ERROR", error: data.errors[0] });
return;
} catch (error) {
dispatch({ type: "LOGIN_ERROR", error: error });
}
}
export async function logout(dispatch) {
dispatch({ type: "LOGOUT" });
localStorage.removeItem("currentUser");
localStorage.removeItem("token");
}
my question is how to expand this to check whether the JWT has expired or not every time the useAuthState() hook is called (if this is even the best way to go about things)? and then log the user out or perhaps refresh the token from the server without having to log the user out if possible.
Thanks in advance.
With JWT, you can decrypt your own token in a browser without a secret key. This way you can check if the JWT token is about or already expired. The secret key is only needed for the authenticity of where it's signed off. This is demonstrated well in JWT website.
If you wanted to be able to regenerate the key from expired JWT you can just set ignoreExpiration to true in jsonwebtoken's verify() function at your server, but then why even bother setting expiration time in the first place? It's best to only allow regenerating JWT when it's about to expire.
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;
},
)