The problem I'm facing my accidents array is empty for some reason, and receiving <h3>You have no accidents </h3>. console.log(accidents._id); this log returns undefined. Thank you in advance for the help and taking a look into this.
Slice:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import accidentService from "./accidentService";
const initialState = {
accidents: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
// Create new accident report
export const createAccident = createAsyncThunk(
"home/create",
async (accidentData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await accidentService.createAccidentReport(accidentData, token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
// Get accident reports
export const getAccidents = createAsyncThunk(
"accidents/getAll",
async (_, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await accidentService.getAccidents(token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const accidentSlice = createSlice({
name: "accident",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(createAccident.pending, (state) => {
state.isLoading = true;
})
.addCase(createAccident.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.accidents.push(action.payload);
})
.addCase(createAccident.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getAccidents.pending, (state) => {
state.isLoading = true;
})
.addCase(getAccidents.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.goals = action.payload;
})
.addCase(getAccidents.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
export const { reset } = accidentSlice.actions;
export default accidentSlice.reducer;
Service:
import axios from "axios";
const API_URL = "/api/home/accidentReport";
// Create new accident report
const createAccidentReport = async (accidentData, token) => {
//Get token
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.post(API_URL, accidentData, config);
return response.data;
};
//Get accident reports
const getAccidents = async (token) => {
//Get token
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.get("/api/home/allAccidents", config);
return response.data;
};
const accidentService = {
createAccidentReport,
getAccidents,
};
export default accidentService;
View:
import "./ReportTable.css";
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getAccidents } from "../../Features/accidentReport/accidentSlice";
const ReportTable = () => {
const dispatch = useDispatch();
const { accidents, isLoading, isError, message } = useSelector(
(state) => state.accident
);
useEffect(() => {
dispatch(getAccidents());
}, [dispatch, getAccidents]);
console.log(accidents._id);
return (
<>
<section>
{accidents.length > 0 ? (
<div>
{accidents.map((accident) => (
<p key={accident._id}>{accident._id}</p>
))}
</div>
) : (
<h3>You have no accidents </h3>
)}
</section>
</>
);
};
export default ReportTable;
Related
PrivateRoute.js
import { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import Spinner from '../layout/Spinner';
const PrivateRoute = ({ children }) => {
const { loading, isAuthenticated } = useSelector((state) => state.auth);
if (loading) return <Spinner />
if (!isAuthenticated) return <Navigate to='/login' />
return children;
}
export default PrivateRoute;
App.js
import './App.css';
import 'react-phone-number-input/style.css'
import { useEffect } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { loadUser } from './reducers/authSlice';
import setAuthToken from './utils/setAuthToken';
import Products from './screens/Products';
import Product from './screens/Product';
import Navigator from './components/layout/Navigator';
import Footer from './components/layout/Footer';
import Signup from './screens/Signup';
import Login from './screens/Login';
import Dashboard from './screens/Dashboard';
import Settings from './screens/Settings';
import PrivateRoute from './components/routing/PrivateRoute';
import Store from './screens/Store';
import CreateOrUpdateStore from './screens/CreateOrUpdateStore';
import AddProduct from './screens/AddProduct';
import UpdateProduct from './screens/UpdateProduct';
const token = localStorage.getItem("token");
if (token) {
setAuthToken(token);
}
function App() {
const dispatch = useDispatch();
const { isAuthenticated } = useSelector((state) => state.auth);
useEffect(() => {
if (!isAuthenticated && token) {
dispatch(loadUser());
}
}, []);
return (
<Router>
<Navigator />
<div className="container">
<Routes>
<Route path="/" element={<Products />} />
<Route path="/products/:id" element={<Product />} />
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
<Route path="/settings" element={<PrivateRoute><Settings /></PrivateRoute>} />
<Route path="/store" element={<PrivateRoute><Store /></PrivateRoute>} />
<Route path="/create-store" element={<PrivateRoute><CreateOrUpdateStore /></PrivateRoute>} />
<Route path="/update-store" element={<PrivateRoute><CreateOrUpdateStore /></PrivateRoute>} />
<Route path="/add-product" element={<PrivateRoute ><AddProduct /></PrivateRoute>} />
<Route path="/update-product/:id" element={<PrivateRoute><UpdateProduct /></PrivateRoute>} />
</Routes>
</div>
<Footer />
</Router>
)
}
export default App;
Login.js
import { useState, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import InputField from '../components/layout/InputField';
import Button from '../components/layout/Button';
import { faEnvelope, faUserCircle, faLock, faArrowRightToBracket } from '#fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { loginUser, clearMessages } from '../reducers/authSlice';
import FormAlert from '../components/layout/FormAlert';
import Spinner from '../components/layout/Spinner';
const Login = () => {
const [ formData, setFormData ] = useState({
email:'',
password:''
});
const { email, password } = formData;
const dispatch = useDispatch();
const { errors, message, loading, isAuthenticated } = useSelector((state) => state.auth);
useEffect(() => {
dispatch(clearMessages());
},[dispatch]);
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const getError = (name) => {
const findError = errors.filter(error => error.param === name);
if (findError.length > 0) {
const error = errors.find(error => error.param === name);
return error;
}
}
const onSubmit = (e) => {
e.preventDefault();
const data = { email, password };
dispatch(loginUser(data));
setTimeout(() => {
dispatch(clearMessages());
},3000);
}
if (loading) return <Spinner />
if (isAuthenticated) return <Navigate to="/dashboard" />
return (
<form onSubmit={(e) => onSubmit(e)}>
<FontAwesomeIcon style={{ backgroundColor: 'inherit', color: '#2596be', marginTop: '20px' }} size='8x' icon={faUserCircle} />
{JSON.stringify(message) !== '{}' ? (<FormAlert alert={message} />) : ''}
<InputField type='text' label='Email' name='email' value={email} changeHandler={onChange} error={getError('email')} icon={faEnvelope} />
<InputField type='password' label='Password' name='password' value={password} changeHandler={onChange} error={getError('password')} icon={faLock} />
<Button text='LOGIN' loading={loading} icon={faArrowRightToBracket} />
</form>
);
}
export default Login;
authSlice.js
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import { authAPI } from '../services/authAPI';
// Function for logging in user
export const loginUser = createAsyncThunk(
"auth/loginUserStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.loginUser(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for loading user data
export const loadUser = createAsyncThunk(
"auth/loadUserStatus",
async (_, { rejectWithValue }) => {
try {
const response = await authAPI.loadUser();
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for signing up a user
export const signupUser = createAsyncThunk(
"auth/signupUserStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.signupUser(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for verifying user accounts after sign up
export const verifyUser = createAsyncThunk(
"auth/verifyUserStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.verifyUser(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for resending otp
export const resendOtp = createAsyncThunk(
"auth/resendOtpStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.resendOtp(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for resending otp
export const retrievePassword = createAsyncThunk(
"auth/retrievePasswordStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.retrievePassword(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for generating authentication token when reseting forgotten password
export const generateToken = createAsyncThunk(
"auth/generateTokenStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.generateToken(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for creating new password
export const createNewPassword = createAsyncThunk(
"auth/createNewPasswordStatus",
async (token, data, { rejectWithValue }) => {
try {
const response = await authAPI.createNewPassword(token, data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for updating user profile
export const updateAccount = createAsyncThunk(
"auth/updateAccountStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.editAccount(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for changing the logged in user password
export const changePassword = createAsyncThunk(
"auth/changePasswordStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.changePassword(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Function for uploading user profile picture
export const photoUpload = createAsyncThunk(
"auth/uploadPhotoStatus",
async (data, { rejectWithValue }) => {
try {
const response = await authAPI.uploadPicture(data);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
)
// The initial state of the reducer
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: false,
loading: false,
message: {},
errors: [],
user: null,
passwordResetToken: '',
accountUpdating: false,
passwordChangeLoading: false,
photoUploadMessage: {},
photoUploadLoading: false
};
// The auth slice
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
clearMessages: (state) => {
state.message = {};
state.errors = [];
state.photoUploadMessage = {};
},
logout: (state) => {
state.token = localStorage.removeItem("token");
state.isAuthenticated = false;
state.user = null;
}
},
extraReducers: (builder) => {
builder.addCase(loginUser.pending, (state, { payload }) => {
state.user = null;
state.message = {};
state.errors = [];
state.loading = true;
});
builder.addCase(loginUser.fulfilled, (state, { payload }) => {
if (payload.status === 'created') {
state.message = payload;
} else {
localStorage.setItem("token", payload.data);
}
state.loading = false;
state.isAuthenticated = true;
});
builder.addCase(loginUser.rejected, (state, { payload }) => {
if (payload.errors.length === 1) {
state.message = payload.errors[0];
} else {
state.errors = payload.errors;
}
state.loading = false;
state.isAuthenticated = false;
});
builder.addCase(loadUser.pending, (state, { payload }) => {
state.errors = [];
state.message = {};
state.loading = true;
state.isAuthenticated = false;
});
builder.addCase(loadUser.fulfilled, (state, { payload }) => {
state.user = payload.data;
state.isAuthenticated = true;
state.loading = false;
});
builder.addCase(loadUser.rejected, (state, { payload }) => {
state.loading = false;
state.message = payload;
state.user = null;
});
builder.addCase(signupUser.pending, (state, { payload }) => {
state.message = {};
state.user = null;
state.errors = [];
state.loading = true;
});
builder.addCase(signupUser.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload;
state.user = { phonenumber: payload.data };
});
builder.addCase(signupUser.rejected, (state, { payload }) => {
state.loading = false;
if (payload.errors.length === 1) {
state.message = payload.errors[0];
} else {
state.errors = payload.errors;
}
});
builder.addCase(verifyUser.pending, (state, { payload }) => {
state.message = {};
state.errors = [];
state.loading = true;
});
builder.addCase(verifyUser.fulfilled, (state, { payload }) => {
state.user = null;
state.message = payload;
state.loading = false;
});
builder.addCase(verifyUser.rejected, (state, { payload }) => {
state.message = payload.errors[0];
state.loading = false;
});
builder.addCase(resendOtp.pending, (state, { payload }) => {
state.message = {};
state.loading = true;
state.errors = [];
});
builder.addCase(resendOtp.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload;
});
builder.addCase(resendOtp.rejected, (state, { payload }) => {
state.loading = false;
state.message = payload.errors[0];
});
builder.addCase(retrievePassword.pending, (state, { payload }) => {
state.message = {};
state.loading = true;
state.errors = [];
});
builder.addCase(retrievePassword.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload;
});
builder.addCase(retrievePassword.rejected, (state, { payload }) => {
state.loading = false;
state.errors = payload.errors;
});
builder.addCase(generateToken.pending, (state, { payload }) => {
state.message = {};
state.passwordResetToken = '';
state.loading = true;
state.errors = [];
});
builder.addCase(generateToken.fulfilled, (state, { payload }) => {
state.loading = false;
state.passwordResetToken = payload.data;
});
builder.addCase(generateToken.rejected, (state, { payload }) => {
state.loading = false;
state.errors = payload.errors;
});
builder.addCase(createNewPassword.pending, (state, { payload }) => {
state.message = {};
state.loading = true;
state.errors = [];
});
builder.addCase(createNewPassword.fulfilled, (state, { payload }) => {
state.loading = false;
state.message = payload;
});
builder.addCase(createNewPassword.rejected, (state, { payload }) => {
state.loading = false;
state.errors.push(payload);
});
builder.addCase(updateAccount.pending, (state, { payload }) => {
state.accountUpdating = true;
});
builder.addCase(updateAccount.fulfilled, (state, { payload }) => {
state.accountUpdating = false;
state.user = payload.data;
const { data, ...rest } = payload;
state.message = rest;
});
builder.addCase(updateAccount.rejected, (state, { payload }) => {
state.accountUpdating = false;
if (payload.errors && payload.errors.length > 0) {
state.errors = payload.errors;
} else {
state.message = payload;
}
});
builder.addCase(changePassword.pending, (state, { payload }) => {
state.passwordChangeLoading = true;
});
builder.addCase(changePassword.fulfilled, (state, { payload }) => {
state.passwordChangeLoading = false;
state.message = payload;
});
builder.addCase(changePassword.rejected, (state, { payload }) => {
state.passwordChangeLoading = false;
if (payload.errors) {
state.errors = payload.errors;
} else {
state.message = payload;
}
});
builder.addCase(photoUpload.pending, (state, { payload }) => {
state.photoUploadLoading = true;
});
builder.addCase(photoUpload.fulfilled, (state, { payload }) => {
state.photoUploadLoading = false;
const { data, ...rest } = payload;
state.photoUploadMessage = rest;
state.user = data;
});
builder.addCase(photoUpload.rejected, (state, { payload }) => {
state.photoUploadLoading = false;
state.photoUploadMessage = payload;
});
}
});
export const { clearMessages, logout } = authSlice.actions;
export default authSlice.reducer;
I tried creating a private route and preventing the loadUser action from running on refresh if the user is already logged in by creating a condition on the App.js; but this method still prove to be ineffective in solving the problem.
Use a loading state that is initially true so that the useEffect in App has a chance to run and check the auth status prior to the PrivateRoute component checking the isAuthenticated state and redirecting. You may also want to initialize isAuthenticated to true of there was a persisted token in localStorage.
authSlice.js
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: !!localStorage.getItem("token"),
loading: true,
message: {},
errors: [],
user: null,
passwordResetToken: '',
accountUpdating: false,
passwordChangeLoading: false,
photoUploadMessage: {},
photoUploadLoading: false
};
const PrivateRoute = ({ children }) => {
const { loading, isAuthenticated } = useSelector((state) => state.auth);
if (loading) return <Spinner />;
if (!isAuthenticated) return <Navigate to='/login' />;
return children;
}
I am building a blog app using react and redux toolkit. I have a problem when I am trying to delete the post, it is removed in my database and I got also the success message in the UI that the Redux toolkit pulls from MongoDB. But the problem is that when I click on the delete button the post is not removed right away and I need to manually reload the page so that it can be removed from the Ui. Can someone point out what am I doing wrong? Thanks. Here below is the logic I am doing:
postSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import postService from './postService';
const initialState = {
posts: [],
isError: false,
isSuccess: false,
isLoading: false,
message: '',
};
// Create a post
export const createPost = createAsyncThunk(
'posts/create',
async (postData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await postService.createPost(postData, token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
// Get all posts
export const getPosts = createAsyncThunk(
'posts/getAll',
async (_, thunkAPI) => {
try {
return await postService.getPosts();
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
// Delete post
export const deletePost = createAsyncThunk(
'posts/delete',
async (id, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await postService.deletePost(id, token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const postSlice = createSlice({
name: 'post',
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(createPost.pending, (state) => {
state.isLoading = true;
})
.addCase(createPost.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state?.posts.push(action.payload);
})
.addCase(createPost.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getPosts.pending, (state) => {
state.isLoading = true;
})
.addCase(getPosts.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.posts = action.payload;
})
.addCase(getPosts.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(deletePost.pending, (state) => {
state.isLoading = true;
})
.addCase(deletePost.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.posts = state.posts.filter((post) => post._id !== action.payload);
console.log(action.payload);
})
.addCase(deletePost.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
export const { reset } = postSlice.actions;
export default postSlice.reducer;
postService.js
import axios from 'axios';
const API_URL = '/api/posts/';
// Create a post
const createPost = async (postData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.post(API_URL, postData, config);
return response.data;
};
// Get all posts
const getPosts = async () => {
const response = await axios.get(API_URL);
return response.data;
};
// Delete a post
const deletePost = async (postId, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.delete(API_URL + postId, config);
return response.data;
};
const postService = {
createPost,
getPosts,
deletePost,
};
export default postService;
post.js
import React from 'react';
import './post.css';
import { FaSmileBeam, FaRegSmile } from 'react-icons/fa';
import { RiEdit2Fill } from 'react-icons/ri';
import { MdDeleteOutline } from 'react-icons/md';
import moment from 'moment';
import { useDispatch } from 'react-redux';
import {
deletePost,
} from '../../features/posts/postSlice';
const Post = ({ post }) => {
const dispatch = useDispatch();
const user = JSON.parse(localStorage.getItem('user'));
return (
<>
<FaRegSmile className="smile__icon" />
</>
);
};
const removePost = () => {
dispatch(deletePost(post._id));
};
const { title, createdAt, body, imageFile, postCreator, author } = post;
return (
<div className="post__container card">
<div className="post__card">
<div className="post__image">
<img src={imageFile} alt="post" />
</div>
<div className="post__content">
<div className="edit__icon">
{author === user?.result?._id && (
<RiEdit2Fill className="edit__icon" fontSize="small" />
)}
</div>
<div className="post__header__title">
<h3>{title}</h3>
</div>
<div className="post__message">
<p>{body}</p>
</div>
<div className="post__header">
<div className="post__header__date">
{user ? (
<span className="user__avatar">
<p>{user?.result?.username.charAt(0).toUpperCase()}</p>
</span>
) : (
''
)}
<span className="post__header__date__text">
{postCreator.toUpperCase()}
<p className="moments">{moment(createdAt).fromNow()}</p>
</span>
</div>
</div>
<div className="post__actions">
<div className="icon__box">
<Smile disabled={!user} />
</div>
<div className="icon__box">
<MdDeleteOutline className="delete__icon" onClick={removePost} />
</div>
</div>
</div>
</div>
</div>
);
};
export default Post;
you need to change following:
const deletePost = async (postId, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.delete(API_URL + postId, config);
if(response.data){
return postId;
}
};
Explanation:
In your case, deletePost service was just returning the success message that resulted in action.payload to be not id of post in following:
.addCase(deletePost.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.posts = state.posts.filter((post) => post._id !== action.payload);
console.log(action.payload);
})
this was causing the issue as your logic relies on filtering the deleted post id that should be provided by action.payload. In the above code I have just returned postId whenever deletePost is successful which provides correct action.payload to above case
I'm trying to fetch a list from database and add to my state. but the action.payload is undefined on the api the result is correct.
mySlice
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { fileFetch } from "./fileAPI";
const initialState = {
loading: "idle" | "pending" | "succeeded" | "failed",
error: false,
filesUploaded: null,
};
export const fetchFiles = createAsyncThunk("files/fetchFiles", () => {
return fileFetch();
});
export const fileSlice = createSlice({
name: "files",
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchFiles.pending, (state, action) => {
state.loading = "pending";
})
.addCase(fetchFiles.fulfilled, (state, action) => {
console.log(action);
state.loading = "succeeded";
state.filesUploaded = action.payload;
})
.addCase(fetchFiles.rejected, (state, action) => {
state.loading = "failed";
state.filesUploaded = [];
state.error = action.error.message;
});
},
});
export default fileSlice.reducer;
myAPI
const api = axios.create({
baseURL: "http://localhost:8081/file/",
headers: {
"content-type": "application/json",
},
});
export const fileFetch = async () => {
await api
.get("getAll")
.then((res) => {
console.log(res.data);
return res.data;
})
.catch((err) => {
throw new Error(err);
});
};
the console.log on the api is returning the correct data.
any idea why the payload is undefined?
thanks.
Might be because you are using both async-await and Promise methods in fileFetch function. Try updating it to this
export const fileFetch = async () => {
const response = await api.get("getAll")
return response.data
};
myApi
export const fileFetch = () => api.get("getAll")
mySlice
export const fetchFiles = createAsyncThunk(
"files/fetchFiles",
async ({ rejectWithValue }) => {
try {
const response = await fileFetch();
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
Also working lifecycle methods
In my project, I want to put as much as logic and function inside the the slice.js. Sometime I want to create or export the function outside of the createSlice like this:
const checkValid = () => {
```need to access the current state to check for validation```
}
export const boardSlice = createSlice({
name: 'board',
initialState,
reducers: {
check: (state, actions) => {
checkValid(actions.payload);
}
}
});
The checkValid need to access the state in the store, my current solution is directly passing the state as props along with the actions.payload in the reducer. But is there a better or official way of doing this? Also, is it good to put as much as logic inside the slice? Much appreciated.
Let me share with you what am using, hope it helps
mport { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import authService from "Services/authService";
import { logOutUser } from "Services/userServices";
const user = JSON.parse(localStorage.getItem("user"));
const initialState = {
user: user ? user : null,
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
// Register user
export const register = createAsyncThunk("user/register", async (user, thunkAPI) => {
try {
return await authService.register(user);
} catch (error) {
const message =
(error.response && error.response.data && error.response.data.message) || error.message || error.toString();
return thunkAPI.rejectWithValue(message);
}
});
// Login user
export const login = createAsyncThunk("user/login", async (user, thunkAPI) => {
try {
const { username, password } = user;
const res = await authService.loginUser(username, password);
return res;
} catch (error) {
const message =
(error.response && error.response.data && error.response.data.message) || error.message || error.toString();
return thunkAPI.rejectWithValue(message);
}
});
export const logout = createAsyncThunk("user/logout", async () => {
try {
return await logOutUser();
return res;
} catch (error) {
const message =
(error.response && error.response.data && error.response.data.message) || error.message || error.toString();
return thunkAPI.rejectWithValue(message);
}
});
export const userSlice = createSlice({
name: "user",
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.isSuccess = true;
localStorage.removeItem("token");
localStorage.removeItem("user");
state.user = null;
});
},
});
export const { reset } = userSlice.actions;
export default userSlice.reducer;
for more clalification on the use of extra reducers
checkout this https://www.youtube.com/watch?v=mvfsC66xqj0
i am trying to send the error messages that sent from my server ( express ) to axios and the error message displays in toastify component but the error message doesn't show up here is the login axios function with the toastify how can i display toastify message inside my page from redux ?
here is my code :
// redux controller
const login = async (username, password) => {
await axios.post("/login",{username,password,},
{ withCredentials: true });};
// reducer page
export function generateError(prop) {
return function (dispatch) {
dispatch({
type: "USER_FAIL"
});
toast.error(prop);
};
}
export function generateSuccess(prop) {
return function (dispatch) {
dispatch({
type: "USER_SUCCESS"
});
toast.success(prop);
};
}
export const login = createAsyncThunk(
"/login",
async ({ username, password }) => {
try {
const data = await authService.login(username, password);
if (data) {
if (data.errors) {
const { username, password } = data.errors;
if (username) generateError(username)
else if (password) generateError(password);
} else {
generateSuccess(data.success);
}
}
return { user: data };
} catch (error) {
console.log(error);
}
}
);
// login page
const handleSubmit = (e) => {
e.preventDefault();
dispatch(login({ username, password }));
}
i am using react-tostify and #redux-toolkit but the message doesn't display inside my page
i fixed it and here is my code :
// auth.js ( redux page )
export const login = createAsyncThunk(
"/login",
async ({ username, password }) => {
try {
const {data} = await axios.post(
"/login",
{
username,
password,
},
{ withCredentials: true }
);
return { user: data };
} catch (error) {
console.log(error);
}
});
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
const authSlice = createSlice({
name: "auth",
initialState,
extraReducers: {
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
}})
const { reducer } = authSlice; export default reducer;
Login Page :
const { isLoggedIn } = useSelector((state) => state.auth);
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
dispatch(login({ username, password })).then(data => {
console.log(data)
if (data.payload.user) {
if (data.payload.user.errors) {
const { username, password } = data.payload.user.errors;
if (username) generateError(username)
else if (password) generateError(password);
} else {
generateSuccess(data.success);
navigate("/dashboard");
}
}
})
}
i realized when i back the data it has an object name payload i used it to get the error messages from express and then i put the message in toastify function gettingError and here it is
const generateError = error => {
toast.error(error, {
position: "bottom-right",
})
}
Hai I'm also looking for the same problem while searching I found a solution at with this : react-toastify-with-redux
my Code : authAction.js
import 'react-toastify/dist/ReactToastify.min.css';
import { toast} from 'react-toastify';
export const registerUser = (userData) => dispatch =>{
axios.post('user/register',userData)
.then(res=>toast.success('Your Account Created Successfully 👍'))
.then(res=> window.location = '/authentication/sign-in')
.catch(err=>dispatch(
{
type: GET_ERRORS,
payload: err.response.data
}
),toast.error("Error 😣"))
// .catch((err)=> {return })
};
On your signUp page just add
<ToastContainer />
That's all ...
This answer is probably late. But I came across this problem and decided to do it my way. I know there is toast. promise to handle promises and I don't want to call dispatch.then every time. So I can up with passing dispatch to my action wrapper. Here is my code.
// utils.ts
type ArgumentTypes<F extends CallableFunction> = F extends (
...args: infer A
) => any
? A[0]
: never;
export const withToast = <T = AnyAction | typeof createAsyncThunk>(
action: T,
{ pending, error, success }: ToastPromiseParams<T>
) => {
return (
dispatch: ReturnType<typeof useAppDispatch>,
actionParams?: ArgumentTypes<T & CallableFunction> | void
) => {
const promise = dispatch(
(action as CallableFunction)(actionParams as any)
).unwrap();
toast.promise(promise, {
pending,
error,
success,
});
};
};
// actions.ts
export const login = createAsyncThunk(
"user/login",
async (payload: {
email: string;
password: string;
}): Promise<Partial<LoginAPIResponse>> => {
const { data } = await axios.post(`${API}/${LOGIN_EP}/`, payload);
return data;
}
);
export const loginWithToast = withToast(login, {
pending: "Logging in...",
error: {
render: (error: any) => {
return error?.password || error?.email
? "Invalid email or password"
: "Something went wrong";
},
},
success: "Logged in successfully",
});
// usage in component
const dispatch = useAppDispatch();
loginWithToast(dispatch, {
email: values.email.value,
password: values.password.value,
});
First createAsyncThunk:
import { coreAxios } from "utilities/axios"; // Own customized axios
import { createAsyncThunk } from "#reduxjs/toolkit";
const BASE_URL = process.env.REACT_APP_MAIN_URL
export const GetProducts = createAsyncThunk(
"inventory/GetProducts",
async () => {
const {data} = await coreAxios.get(`${BASE_URL}/api/product/list/`);
return data
}
);
Second createSlice:
import { createSlice } from "#reduxjs/toolkit";
import { GetProducts } from "services/inventory/product.service";
import { toast } from 'react-toastify';
export const productSlice = createSlice({
name: "products",
initialState: {
productsList: [],
productsLoading: false,
productsError: null,
},
extraReducers:
(builder) => {
builder.addCase(GetProducts.pending, (state) => {
toast.loading('Promise is pending...')
state.productsLoading = true
});
builder.addCase(GetProducts.fulfilled, (state, action) => {
toast.dismiss();
toast.success('Promise resolved 👌');
state.productsList = action.payload
state.productsLoading = false
state.productsError = null
});
builder.addCase(GetProducts.rejected, (state, action) => {
toast.dismiss();
toast.error('Promise rejected 🤯 😣')
state.productsLoading = false
state.productsError = action.error?.message
});
},
});
export default productSlice.reducer;
Third page:
import { ToastContainer } from 'react-toastify';
import { useSelector, useDispatch } from "react-redux";
import { GetProducts } from 'services/inventory/product.service';
const Product = () => {
const { productsList, productsLoading, productsError } = useSelector((state) => state.products);
const dispatch = useDispatch();
useEffect(() => {
dispatch(GetProducts());
}, []);
return (
<div className="grid crud-demo">
<h1>Hello Alim</h1>
<ToastContainer />
</div>
);
}