I want to create createAsyncThunk function for register user but there is a problem when rejected action, it is throw an error
the code of authSlice:
`
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
reset: (state) => {
state.isLoading = false;
state.isSuccess = false;
state.isError = false;
state.message = "";
},
},
extraReducers: (builder) => {
builder
.addCase(register.pending, (state) => {
state.isLoading = true;
})
.addCase(register.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.user = action.payload;
})
.addCase(register.rejected, (state, action) => {
console.log({ action });
state.isLoading = false;
state.isError = true;
state.message = action.payload;
state.user = null;
})
},
});
the code of function register user
// Register user
export const register = createAsyncThunk(
"auth/register",
async (user, thunkAPI) => {
const { rejectWithValue } = thunkAPI;
try {
return await authService.register(user);
} catch (error) {
const message = error.message;
return rejectWithValue(message);
}
}
);
import axios from "axios";
const API_URL = "http://localhost:5000/auth/condidate";
const register = async (userData) => {
const response = await axios.post(API_URL, userData);
if (response.data) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
};
const authService = {
register,
};
export default authService;
`
there is no problem with register fulfilled but rejected regisiter throw an error
enter image description here
try to register user with react and redux toolkit but there is a problem with reject the action of register
Related
I'm creating a CRUD project with React and Redux toolkit and the problem is I can't update Comments after creating a new one.
I structured Comment Slice like that:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import commentsService from "./commentService";
const initialState = {
comments: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
export const createComment = createAsyncThunk(
"api/newcomment",
async (commentData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await commentsService.createComment(commentData, token);
} catch (error) {
console.log(error);
}
}
);
export const getAllComments = createAsyncThunk(
"api/comments",
async (PostId, thunkAPI) => {
const token = thunkAPI.getState().auth.user.token;
return await commentsService.getAllComments(PostId, token);
}
);
export const deleteComment = createAsyncThunk(
"api/:id",
async (id, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await commentsService.deleteComment(id, token);
} catch (error) {
console.log(error);
}
}
);
export const commentsSlice = createSlice({
name: "comment",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(createComment.pending, (state) => {
state.isLoading = true;
})
.addCase(createComment.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.comments = state.comments.push(action.payload);
})
.addCase(createComment.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getAllComments.pending, (state) => {
state.isLoading = true;
})
.addCase(getAllComments.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
const comments = state.comments.filter(
(comment) => comment.PostId === action.payload.PostId
);
if (comments.length === 0) {
state.comments.push(action.payload);
}
})
.addCase(getAllComments.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(deleteComment.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteComment.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.comments = state.comments.filter(
(comment) => comment.id !== action.payload.id
);
})
.addCase(deleteComment.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
And CardPost where I dispatch comments:
const CardPost = ({ post }) => {
const [show, setShow] = useState(false);
const dispatch = useDispatch();
const { user } = useSelector((state) => state.auth);
const PostId = post.id;
const { comments, isLoading, isError, message } = useSelector(
(state) => state.comments
);
useEffect(() => {
if (isError) {
console.log(message);
}
dispatch(getAllComments(PostId));
}, [dispatch, PostId, isError, message]);
//// Show Comments
<div>
<div>
<FormComment PostId={PostId} />
</div>
<div>
{comments.length > 0 ? (
<div className="comments">
{comments.map((id) => (
<div key={id}>
<CardComment post={post} />
</div>
))}
</div>
) : (
<Typography>No Comment..</Typography>
)}
</div>
</div>
When there are several comments on a Post that are displayed and I create a new one, it shows me the message "No comment.." but all the messages appear well when the page is refreshed.
Where did I make it wrong?
Is it the .addCase that I built wrong and the state is not updated or is it the useEffect dispatch?
The goal is to basically "logout", which means removing the JWT that's currently stored in local storage.
Ideally, what I want is that the logout button is clicked, the logout function is dispatched which is essentially just removing the JWT from local storage, the reset function is dispatched which means that the state is reverted back to the initial state, and finally, it will navigate onto the login page via the useNavigate hook.
The problem is, it can't seem to work. Oddly enough, I tried testing out if it's reading anything on the selector
Here is the code for the dashboard, which checks if there's an existing user in local storage, and if null, redirects to the login page:
const navigate = useNavigate();
const dispatch = useDispatch();
const { user } = useSelector((state) => state.auth);
const { posts, isLoading, isError, message } = useSelector(
(state) => state.post
);
useEffect(() => {
if (isError) {
console.log(message);
}
if (!user) {
navigate("/login");
}
dispatch(getPosts());
return () => {
dispatch(reset());
};
}, [user, message, isError, navigate, dispatch]);
if (isLoading) {
return <Spinner />;
}
here's the logout function from the slice as well as the slice containing the reset:
export const logout = createAsyncThunk("auth/logout", async () => {
await authService.logout();
});
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
reset: (state) => {
state.isLoading = false;
state.isSuccess = false;
state.isError = false;
state.message = "";
},
},
extraReducers: (builder) => {
builder
.addCase(register.pending, (state) => {
state.isLoading = true;
})
.addCase(register.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.user = action.payload;
})
.addCase(register.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
state.user = null;
})
.addCase(login.pending, (state) => {
state.isLoading = true;
})
.addCase(login.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.user = action.payload;
})
.addCase(login.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
state.user = null;
})
.addCase(logout.fulfilled, (state) => {
state.user = null;
});
},
});
Here's the function that handles the logout button click event:
const handleLogout = () => {
dispatch(logout());
dispatch(reset());
navigate("/");
};
Edit:
The returned callback function in the first set of codes is basically the same all throughout, it just reverts the state into the default state (null values).
The logout action is asynchronous, and the logout.fulfilled action is what clears the state.user back to null. The handleLogout function doesn't wait for the Promise to resolve though, it just dispatches the two actions and immediately navigates to "/". It's likely the case that the logout logic hasn't completed and reset the user state when the UI is checking the user state on the dashboard.
It seems that handleLogout should wait for the logout action to resolve, then issue the other action dispatch and navigate.
export const logout = createAsyncThunk("auth/logout", async () => {
return await authService.logout(); // <-- return Promise
});
const handleLogout = async () => {
await dispatch(logout()); // <-- await Promise to resolve
dispatch(reset());
navigate("/");
};
i got some problem for a couple hours, i really dont know why useeffect give me infinite loop when i dispatch the redux action ? because i want my page is will refresh after i change the value from order state. is there not allowed to put the async state from redux in useeffect? sorry im still new..
orderSlice.js
export const orderPay = createAsyncThunk(
'order/orderPay',
async (order, thunkAPI) => {
try {
const user1 = thunkAPI.getState().user.userInfo;
const config = {
headers: {
'Content-Type': 'Application/json',
Authorization: `Bearer ${user1.token}`,
},
};
const res = await axios.put(
`http://localhost:8000/api/order/${order.orderId}/pay`,
order.paymentResult,
config
);
const data = await res.data;
return data;
} catch (error) {
thunkAPI.rejectWithValue(
error.response && error.response.data.message
? error.response.data.message
: error.response
);
}
}
);
const orderSlice = createSlice({
name: 'order',
initialState: {
loading: false,
error: '',
order: {},
success: false,
},
reducers: {
orderDetail: (state, action) => {
state.order = action.payload;
},
orderPayReset: (state, action) => {
state.loading = false;
state.error = '';
state.order = {};
state.success = false;
},
},
extraReducers: (builder) => {
builder
.addCase(postOrder.pending, (state, action) => {
state.loading = true;
state.order = {};
state.success = false;
})
.addCase(postOrder.fulfilled, (state, action) => {
state.loading = false;
state.order = action.payload;
state.success = true;
})
.addCase(postOrder.rejected, (state, action) => {
state.error = action.payload;
})
.addCase(orderDetail.pending, (state, action) => {
state.loading = true;
state.order = {};
state.success = false;
})
.addCase(orderDetail.fulfilled, (state, action) => {
state.loading = false;
state.order = action.payload;
state.success = true;
})
.addCase(orderDetail.rejected, (state, action) => {
state.error = action.payload;
})
.addCase(orderPay.pending, (state, action) => {
state.loading = true;
state.success = false;
})
.addCase(orderPay.fulfilled, (state, action) => {
state.loading = false;
state.success = true;
})
.addCase(orderPay.rejected, (state, action) => {
state.error = action.payload;
});
},
});
OrderScreen.jsx
const OrderScreen = () => {
const navigate = useNavigate();
const { orderId } = useParams();
const [sdkReady, setSdkReady] = useState(false);
const { paymentMethod } = useSelector((state) => state.cart);
const { userInfo } = useSelector((state) => state.user);
const { success, loading, error, order, orderPaid } = useSelector(
(state) => state.order
);
const dispatch = useDispatch();
useEffect(() => {
const addPaypalScript = async () => {
const res = await axios.get(`http://localhost:8000/api/config/paypal`);
const data = await res.data;
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `https://www.paypal.com/sdk/js?client-id=${data}`;
script.async = true;
script.onload = () => {
setSdkReady(true);
};
document.body.appendChild(script);
return data;
};
(async () => await addPaypalScript())();
if (!order || success) {
dispatch(orderPayReset());
dispatch(orderDetail(orderId));
} else if (!order.isPaid) {
if (!window.paypal) {
addPaypalScript();
} else {
setSdkReady(true);
}
}
}, [orderId, dispatch]);
const successPaymentHandler = (paymentResult) => {
dispatch(orderPay({ orderId, paymentResult }));
order.isPaid = true;
};
// console.log(orderPaid);
return loading ? (
<h1>Order {order._id}</h1>
)
I am using redux toolkit and ran into an issue where I cannot push the results into an array which I declared. This is my initial state:
const initialState = {
favorites: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
In the dashboard of my application, I want to display the favorites that are in state in which I use a useEffect as such:
useEffect(() => {
if (isError) {
console.log(message);
}
if (!user) {
navigate("/login");
}
//dispatch(getFavorites());
// return () => {
// dispatch(reset());
// };
}, [user, navigate, isError, message, dispatch]);
The problem occurs when I uncomment the dispatch to get favorites, here is the getFavorites in my service:
//Get user favorites
const getFavorites = async (token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.get(API_URL, config);
return response.data;
};
Here is how I create a favorite:
const createFavorite = async (favoriteData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
var qs = require("qs");
var data = qs.stringify({
address: favoriteData.location["address1"],
phone: favoriteData.display_phone,
rating: favoriteData.rating.toString(),
price: favoriteData.price,
});
console.log(favoriteData);
const response = await axios.post(API_URL, data, config);
return response.data;
};
This is where the error occurs in the slice file where my extra-reducers are:
export const favoriteSlice = createSlice({
name: "favorite",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(createFavorite.pending, (state) => {
state.isLoading = true;
})
.addCase(createFavorite.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.favorites.concat(action.payload);
})
.addCase(createFavorite.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getFavorites.pending, (state) => {
state.isLoading = true;
})
.addCase(getFavorites.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.favorites = action.payload;
})
.addCase(getFavorites.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
The errors occurs on the line:
state.favorites.concat(action.payload)
Am I returning the data in a wrong format? I am new to redux and am following a tutorial so I am not sure how to fix this. Any help would be appreciated.
.concat() doesn't mutate it returns a copy so you have to also have to reset your state.favorites to it here:
.addCase(createFavorite.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.favorites = state.favorites.concat(action.payload);
})
As for the error, it seems your state.favorites either goes undefined or its set to a non-array/string value. .concat() only works on arrays or strings
I would know how to make user authentication properly. Could you tell me what's the logic behind it?
My environment:
React with TypeScript
reduxjs/toolkit
Node.js with Express
I have implemented this functionality but this is not working as expected. My component are rendering many times and when i refresh the page router navigates me always to the homepage.
I know this is wrong approach because at the begining state is always null.
On my app.tsx every time i'm checking if token exists on localStorage, if not then get token and authUser.
useEffect(() => {
if (token) return;
if (getToken()) dispatch(authUser());
}, [dispatch, currentUser, token]);
For my routes i have created ProtectedRouteComponent
But in this scenario currentUser is null at the begining (when initial state is null) so router navigates to '/auth' because async thunk need time to fullfill auth.
const ProtectRoute = ({ children }: { children: JSX.Element }) => {
const { currentUser, isFetching } = useAppSelector(state => state.auth);
return isFetching ? <LoadingSpinner /> : currentUser ? children : <Navigate to="/auth" />;
};
SignIn Method
export const signIn = createAsyncThunk<ILoggedUser, IUserCredentials>(
'auth/signIn',
async (userCredentials, thunkAPI) => {
try {
const response: {
status: any;
data: {
token: string;
data: {
id: string;
name: string;
role: string;
avatarUrl: string;
};
};
} = await api.post('/users/login', userCredentials);
if (response.status === 200) {
localStorage.setItem('token', response.data.token);
return response.data.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
console.log('Error: ', e);
return thunkAPI.rejectWithValue(e);
}
}
);
AuthUser Method
export const authUser = createAsyncThunk<IAuthenticatedUser>(
'auth/userAuthentication',
async (_, { rejectWithValue }) => {
try {
const accessToken = getToken();
if (!accessToken) rejectWithValue('Invalid token');
const config = {
headers: { Authorization: `Bearer ${accessToken}` },
};
const response: IAuthUser = await api.get('/users/authenticateUser', config);
const userData = response.data.data.data;
return {
userData,
accessToken,
};
} catch (error) {
removeToken();
return rejectWithValue(error);
}
}
);
AuthSlice
export const authSlice = createSlice({
name: 'auth/signIn',
initialState,
reducers: {
signOut(state) {
state.currentUser = null;
},
},
extraReducers: builder => {
builder.addCase(signIn.pending, state => {
state.isFetching = true;
state.errorMessage = null;
});
builder.addCase(signIn.fulfilled, (state, action) => {
state.isFetching = false;
state.currentUser = action.payload;
});
builder.addCase(signIn.rejected, state => {
state.isFetching = false;
state.errorMessage = 'Niepoprawny adres email lub hasło.';
});
builder.addCase(authUser.pending, state => {
state.isFetching = true;
});
builder.addCase(authUser.fulfilled, (state, action) => {
const { accessToken, userData }: IAuthenticatedUser = action.payload;
state.token = accessToken;
state.currentUser = userData;
state.isFetching = false;
});
builder.addCase(authUser.rejected, state => {
state.token = '';
state.currentUser = null;
state.isFetching = false;
});
builder.addCase(signOut.fulfilled, state => {
state.token = '';
state.currentUser = null;
state.isFetching = false;
});
},
});