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
Related
I'm making a goal setter app inspired from Brad Traversy's MERN stack guide but instead, I used NextJS w/ TypeScript instead of just plain ReactJS since I want to get my feet wet on those as well as I learn MERN.
My problem is that my Redux state: goals keeps on storing another array variable that's named goals and actually has the goals which I need to map on my frontend.
import { createSlice, createAsyncThunk, PayloadAction } from "#reduxjs/toolkit";
import goalService from "./goalService";
type stateTypes = {
goals: any[];
isError: boolean;
isSuccess: boolean;
isLoading: boolean;
message: String;
};
const initialState: stateTypes = {
goals: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
export const addGoal = createAsyncThunk(
"goals/add",
async (goalData, thunkAPI: any) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await goalService.addGoal(goalData, token);
} catch (error: any) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
// Get user goals
export const getGoals = createAsyncThunk(
"goals/getAll",
async (_, thunkAPI: any) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await goalService.getGoals(token);
} catch (error: any) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const goalSlice = createSlice({
name: "goal",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(addGoal.pending, (state) => {
state.isLoading = true;
})
.addCase(addGoal.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.goals.push(action.payload);
})
.addCase(addGoal.rejected, (state, action: PayloadAction<any>) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getGoals.pending, (state) => {
state.isLoading = true;
})
.addCase(getGoals.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.goals = action.payload;
})
.addCase(getGoals.rejected, (state, action: PayloadAction<any>) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
});
},
});
export const { reset } = goalSlice.actions;
export default goalSlice.reducer;
Here is a screenshot from my react devtools showing the tree of goals:
I cloned Brad Traversy's code in my PC but it seems to have no problem about this as I tried to run his code and I even tried copying to how he coded it but to no avail, the problem still persists to mine. I hope someone can see this and help me.
I need the answer to this cause I can't map my state: goals as it just contains another array.
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;
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 am using redux toolkit and firebase firestore for backend. I just want to get an array of objects from the database. Below is the code for slice. When I log the payload in the console I am unable to get the data. Thanks in advance.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import firestore from '#react-native-firebase/firestore';
export const getCarouselImages = createAsyncThunk("/carouselImages", () =>
firestore().collection('Users').get()
);
const initialState = {
isLoading: false,
failed: true,
success: false,
imageArray:[],
};
const carouselData = createSlice({
name: "carouselImageSlice",
initialState,
reducers: {
resetCarouselData: () => initialState,
},
extraReducers: (builder) => {
builder.addCase(getCarouselImages.fulfilled, (state, { payload }) => {
state.isLoading = false;
state.failed = false;
state.success = true;
payload.forEach(doc => {
state.userData.push(doc.data())
});
console.log(payload)
});
builder.addCase(getCarouselImages.rejected, (state, action) => {
state.isLoading = false;
state.failed = true;
state.success = false;
});
builder.addCase(getCarouselImages.pending, (state, { payload }) => {
state.isLoading = true;
state.failed = false;
state.success = false;
});
},
});
export const { resetCarouselData } = carouselData.actions;
export default carouselData.reducer;
For the first time, I am using Redux in my React project. The code here I have added is for cookie-based authentication. I am worried that everything is here is in the correct format. It seems lots of duplicate code here. Especially for pending and rejected status in createSlice portion. How can I refactor this code and what will be the correct coding style in this case?
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import API from "../API";
// Register user:
export const signup = createAsyncThunk(
"user/signup",
async (userInfo, { rejectWithValue }) => {
try {
const { data } = await API.post("/signup", userInfo);
return data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Login:
export const login = createAsyncThunk(
"user/login",
async (loginInfo, { rejectWithValue }) => {
try {
const { data } = await API.post("/login", loginInfo);
return data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Logout:
export const logout = createAsyncThunk(
"user/logout",
async (args, { rejectWithValue }) => {
try {
const { data } = await API.get("/logout");
return data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// Chek-Auth:
export const isAuthenticated = createAsyncThunk(
"user/isAuthenticated",
async (args, { rejectWithValue }) => {
try {
const { data } = await API.get("/check-auth");
return data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// createSlice portion is here:
export const userSlice = createSlice({
name: "user",
initialState: {
loading: true,
isLoggedIn: false,
message: "",
user: null,
error: null,
},
reducers: {},
extraReducers: {
[signup.pending]: (state, action) => {
state.loading = true;
},
[signup.fulfilled]: (state, action) => {
state.loading = false;
state.isLoggedIn = true;
state.message = action.payload.message;
state.user = action.payload.user;
state.error = null;
},
[signup.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload || action.error;
},
[login.pending]: (state, action) => {
state.loading = true;
},
[login.fulfilled]: (state, action) => {
state.loading = false;
state.isLoggedIn = true;
state.message = action.payload.message;
state.user = action.payload.user;
state.error = null;
},
[login.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload || action.error;
},
[logout.pending]: (state, action) => {
state.loading = true;
},
[logout.fulfilled]: (state, action) => {
state.loading = false;
state.isLoggedIn = false;
state.message = action.payload.message;
state.user = null;
},
[logout.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload || action.error;
},
[isAuthenticated.pending]: (state, action) => {
state.loading = true;
},
[isAuthenticated.fulfilled]: (state, action) => {
state.loading = false;
state.isLoggedIn = true;
state.message = action.payload.message;
state.user = action.payload.user;
},
[isAuthenticated.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload || action.error;
},
},
});
// export const { } = userSlice.actions;
export default userSlice.reducer;
We generally recommend to use the builder notation, not the object notation. That makes stuff like this easier:
extraReducers: builder => {
for (const thunk in [signup, login, logout, isAuthenticated]) {
builder.addCase(thunk.pending, (state) => { state.loading = true })
builder.addCase(thunk.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || action.error;
})
}
}
Keep in mind though that putting many asynchronous actions in the same state like you do here, sharing a loading state, may lead to race conditions.
Generally, for api cache stuff you should take a look into Redux Toolkit's Api cache abstraction, RTK-Query:
https://redux-toolkit.js.org/rtk-query/overview