I am trying to create a scraping application using redux toolkit for learning purposes but the scraping process fails whenever I pass custom parameters in the dispatch statement but works correctly on passing default parameters in the thunk
My async thunk
export const loadData = createAsyncThunk(
"alldata/getdata",
async ({ pageNo, language }, thunkAPI) => {
const data = await fetch(
`http://localhost:5000/scrape?pageNo=${encodeURIComponent(
pageNo
)}&language=${encodeURIComponent(language)}`
);
const json = data.json();
return json;
}
);
My slice
const projectSlice = createSlice({
name: "allprojects",
state: {
projectState: [],
workingState: [],
isLoading: false,
hasError: false,
},
reducers: {
addProject: (state, action) => {
return state.workingState.push(action.payload);
},
removeProject: (state, action) => {
return state.workingState.filter(
(project) => project.id !== action.payload.id
);
},
},
extraReducers: {
[loadData.pending]: (state, action) => {
state.isLoading = true;
state.hasError = false;
},
[loadData.fulfilled]: (state, action) => {
const { json } = action.payload;
state.isLoading = false;
state.hasError = false;
},
[loadData.rejected]: (state, action) => {
state.isLoading = false;
state.hasError = true;
},
},
});
export const { addProject, removeProject } = projectSlice.actions;
export const { Projectreducer } = projectSlice.reducer;
export const selectAllPosts = (state) => state.allprojects.projectState;
Calling the async action
React.useEffect(() => {
console.log(dispatch(loadData(1, "ruby")));
}, []);
//url:https://github.com/search?p=undefined&q=language%3Aundefined
how do I solve this error
The async thunk arguments must be collected in one object:
dispatch(loadData({ pageNo: 1, language: "ruby" }))
See https://redux-toolkit.js.org/api/createAsyncThunk#payloadcreator
Related
So I'm new to React and Redux Toolkit, I've been trying to get some 'posts' from my localhost API, I do get the payload and it gets displayed in the Redux Dev Tools, nevertheless I can't get this payload to be put on the state.
My postSlice.js :
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
export const getPosts = createAsyncThunk("posts", async () => {
try {
const res = await axios({
method: "get",
url: `${process.env.REACT_APP_API_URL}api/post`,
withCredentials: true,
});
console.log(res.data);
return res.data;
} catch (err) {
return err.res.data;
}
});
const postsSlice = createSlice({
name: "posts",
initialState: {
posts: [],
loading: false,
error:"",
},
extrareducers: {
[getPosts.pending]: (state, action) => {
state.loading = true;
},
[getPosts.fulfilled]: (state, action) => {
console.log(action.payload);
state.loading = false;
state.posts = action.payload;
},
[getPosts.rejected]: (state, action) => {
state.loading = false;
state.posts = action.payload.message;
},
},
});
export default postsSlice.reducer;
Then I use the dispatch on a React Component
import { useDispatch } from "react-redux";
import { getPosts } from "../redux/features/postSlice";
const Thread = () => {
const [loadPost, setLoadPost] = useState(true);
const dispatch= useDispatch();
useEffect(() => {
if (loadPost ) {
dispatch(getPosts());
setLoadPost(false)
}
}, [loadPost, dispatch]);
return <div>Fil d'actualités</div>;
};
export default Thread;
Finally I get this on the State in Redux Dev Tool
posts(pin):[]
loading(pin): false
error(pin): ""
And this on the Action:
payload (pin): [{...}{...}{...}]
Also, through the pending, fulfilled and rejected states, loading won't change a bit, even if I pass it on true or false directly with VSCode nor the log I put on the fulfilled extra reducer, it's like the action doesn't affect the state at all, therefore I have the another reducer working fine with the async functions, any help would be much appreciated!
EDIT: My bad, i made a misspelling mistake, 'r' instead of 'R' in 'extraReducers' declaration, the kind of error you feel so dumb about
I think you should use extraRedcuer that way:
extraReducers: (builder) => {
builder.addCase(getPosts.pending, (state, action) => {
state.loading = true;
}),
builder.addCase(getPosts.fulfilled, (state, action) => {
console.log(action.payload);
state.loading = false;
state.posts = action.payload;
}),
builder.addCase(getPosts.rejected, (state, action) => {
state.loading = false;
state.posts = action.payload.message;
}),
},
I am working on a MERN app and I have a problem when updating items. I am getting rejections when sending a patch request and there is not much info for debugging to solve the problem. I will appreciate it if someone can point out some logic that is not correct in my code. Thank you in advance.
Here below is the logic I have implemented.
postService.js:
import axios from 'axios';
const API_URL = '/api/posts/';
const updatePost = async (postId, postData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.patch(`${API_URL}/${postId}/`, postData, config);
if (response.data) {
return {
...response.data,
id: postId,
};
}
};
postSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import postService from './postService';
const initialState = {
posts: [],
isError: false,
isSuccess: false,
isLoading: false,
message: '',
};
export const updatePost = createAsyncThunk(
'posts/updatePost',
async (id, postData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await postService.updatePost(id, postData, token);
} catch (error) {
const message =
(error.response.data.message) ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const postSlice = createSlice({
name: 'post',
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(updatePost.pending, (state) => {
state.isLoading = true;
})
.addCase(updatePost.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.posts = state.posts.map((post) =>
post.id === action.payload.id ? action.payload : post
);
})
.addCase(updatePost.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
});
},
});
export const selectAllPosts = (state) => state.posts.posts;
export const { reset } = postSlice.actions;
export default postSlice.reducer;
Form.js:
const Form = ({ postId, setPostId }) => {
const [formData, setFormData] = useState({
postCreator: '',
title: '',
body: '',
imageFile: '',
});
const dispatch = useDispatch();
const user = JSON.parse(localStorage.getItem('user'));
const post = useSelector((state) =>
postId ? state.posts.posts.find((post) => post._id === postId) : null
);
useEffect(() => {
if (post) setFormData(post);
}, [post]);
const clearPost = () => {
setPostId(0);
setFormData({
postCreator: '',
title: '',
body: '',
imageFile: '',
});
};
const handleSubmit = async (e) => {
e.preventDefault();
if (
!formData.postCreator &&
!formData.title &&
!formData.body &&
!formData.imageFile
) {
toast.warning(
'Please fill out all fields, and make sure you are also logged in'
);
} else if (postId) {
dispatch(updatePost(postId, formData));
console.log(postId);
} else {
dispatch(createPost(formData));
clearPost();
setPostId(null);
}
clearPost();
};
The second param of createAsyncThunk is the payloadCreator.
The first param of the payloadCreator is the arguments. The second param of payloadCreator is thunkAPI.
So you should combine id and postData into a single object to represent the arguments.
Update postSlice.js:
export const updatePost = createAsyncThunk(
'posts/updatePost',
async ({id, postData}, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await postService.updatePost(id, postData, token);
} catch (error) {
const message =
(error.response.data.message) ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
Update where you dispatch the updatePost thunk:
updatePost({
id: 123,
postData: {
foo: 'bar'
}
})
I am working on a MERN app with redux toolkit. Currently, I am facing a problem with my update functionality, when I click on the update button I can see in redux dev tools the request is rejected and in the console, the id is showing undefined while I am passing it. I am probably missing something in my code, if someone can point it out and explain that would be great. Thanks in advance. Here below are my code:
postService.js:
import axios from 'axios';
const API_URL = '/api/posts/';
const updatePost = async (_id, postData) => {
const response = await axios.patch(API_URL + _id, postData);
return response.data;
};
const postService = {
updatePost,
};
export default postService;
postSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import postService from './postService';
const initialState = {
posts: [],
isError: false,
isSuccess: false,
isLoading: false,
message: '',
};
export const updatePost = createAsyncThunk(
'posts/updatePost',
async ({ id, postData }, thunkAPI) => {
const { postCreator, title, body, imageFile } = postData;
try {
return await postService.updatePost(id, {
postCreator,
title,
body,
imageFile,
});
} catch (error) {
console.log(error.message);
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(updatePost.pending, (state) => {
state.isLoading = true;
})
.addCase(updatePost.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
const { id, postCreator, title, body, imageFile } = action.payload;
const existingPost = state.find((post) => post.id === id);
if (existingPost) {
existingPost.postCreator = postCreator;
existingPost.title = title;
existingPost.body = body;
existingPost.imageFile = imageFile;
}
})
.addCase(updatePost.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
export default postSlice.reducer;
Form.js:
const Form = ({ activeId, setActiveId }) => {
const [postData, setPostData] = useState({
postCreator: '',
title: '',
body: '',
imageFile: '',
});
const post = useSelector((state) =>
activeId ? state.posts.posts.find((post) => post._id === activeId) : null
);
const user = JSON.parse(localStorage.getItem('user'));
const dispatch = useDispatch();
useEffect(() => {
if (post) setPostData(post);
}, [post]);
const clearInputField = () => {
setActiveId(0);
setPostData({
postCreator: '',
title: '',
body: '',
imageFile: '',
});
};
const handleSubmit = async (e) => {
e.preventDefault();
if (activeId) {
dispatch(updatePost({ activeId, postData }));
clearInputField();
} else {
dispatch(createPost(postData));
clearInputField();
}
};
In the updatePost thunk in postSlice.js, you are attempting to destructure the variables { id, postData } from the payload creator args.
But in Form.js, you are sending an object { activeId, postData } when you dispatch updatePost.
So both id and postData will be undefined because neither exist on the object.
You could change it to:
dispatch(updatePost({id: activeId, postData: formData}))
I'm currently struggling to solve this error which i believe might be a circular reference dependency issue within my react redux application. It was working fine at first but then all of a sudden my app is throwing this error:
From the error, it says checkAuth cannot be accessed before initialisation. checkAuth is a createAsyncThunk that is exported from authentication slice and is imported into the application slice to be added as extra reducers. Right now I am very confused on how to solve this issue. Hopefully you guys could help me out.
Here are the codes:
store.js
import { configureStore } from "#reduxjs/toolkit";
import authReducer from "./authentication/authenticationSlice";
import appReducer from "./application/appSlice";
import errorReducer from "./error/errorSlice";
const store = configureStore({
reducer: {
auth: authReducer,
error: errorReducer,
app: appReducer,
},
});
export default store;
authenticationSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { axiosPublic, axiosPrivate } from "../../utils/axios";
const initialState = {
isAuthLoading: false,
authUser: null,
};
const checkAuth = createAsyncThunk("auth/checkAuth", async (_, thunkAPI) => {
try {
const response = await axiosPrivate.get("/auth");
return response.data;
} catch (e) {
return thunkAPI.rejectWithValue(e.response?.data || e);
}
});
const login = createAsyncThunk("auth/login", async (credentials, thunkAPI) => {
try {
const response = await axiosPublic.post("/auth/login", credentials, {
withCredentials: true,
});
return response.data;
} catch (e) {
return thunkAPI.rejectWithValue(e.response?.data || e);
}
});
const logout = createAsyncThunk("auth/logout", async (_, thunkAPI) => {
try {
await axiosPrivate.post("/auth/logout", null, { withCredentials: true });
return;
} catch (e) {
return thunkAPI.rejectWithValue(e.response?.data || e);
}
});
const reqOTPHandler = createAsyncThunk(
"auth/reqOTPHandler",
async ({ endpoint, credentials }, thunkAPI) => {
try {
await axiosPublic.post(endpoint, credentials);
return;
} catch (e) {
return thunkAPI.rejectWithValue(e.response?.data || e);
}
}
);
const signup = createAsyncThunk(
"auth/submitOTP",
async (credentials, thunkAPI) => {
try {
const response = await axiosPublic.post(
"/auth/confirm-signup",
credentials,
{
withCredentials: true,
}
);
return response.data;
} catch (e) {
return thunkAPI.rejectWithValue(e.response?.data || e);
}
}
);
const reqResetHandler = createAsyncThunk(
"/auth/reqResetHandler",
async ({ endpoint, token }, thunkAPI) => {
try {
await axiosPublic.post(endpoint, {
token,
});
return;
} catch (e) {
return thunkAPI.rejectWithValue(e.response?.data || e);
}
}
);
const resetPassword = createAsyncThunk(
"auth/resetPassword",
async ({ endpoint, credentials }, thunkAPI) => {
try {
await axiosPublic.post(endpoint, credentials);
return;
} catch (e) {
return thunkAPI.rejectWithValue(e.response?.data || e);
}
}
);
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
loading: (state) => {
state.isAuthLoading = true;
},
idle: (state) => {
state.isAuthLoading = false;
},
updateAuthUser: (state, action) => {
state.authUser = action.payload;
},
removeAuthUser: (state) => {
state.authUser = null;
},
},
extraReducers: {
[checkAuth.fulfilled]: (state, action) => {
state.authUser = action.payload;
},
[login.pending]: (state) => {
state.isAuthLoading = true;
},
[login.fulfilled]: (state, action) => {
state.isAuthLoading = false;
state.authUser = action.payload;
},
[login.rejected]: (state, action) => {
state.isAuthLoading = false;
},
[logout.fulfilled]: (state, action) => {
state.authUser = null;
},
[reqOTPHandler.pending]: (state) => {
state.isAuthLoading = true;
},
[reqOTPHandler.fulfilled]: (state) => {
state.isAuthLoading = false;
},
[reqOTPHandler.rejected]: (state) => {
state.isAuthLoading = false;
},
[signup.pending]: (state) => {
state.isAuthLoading = true;
},
[signup.fulfilled]: (state, action) => {
state.isAuthLoading = false;
state.authUser = action.payload;
},
[signup.rejected]: (state) => {
state.isAuthLoading = false;
},
[reqResetHandler.pending]: (state) => {
state.isAuthLoading = true;
},
[reqResetHandler.fulfilled]: (state, action) => {
state.isAuthLoading = false;
},
[reqResetHandler.rejected]: (state) => {
state.isAuthLoading = false;
},
[resetPassword.pending]: (state) => {
state.isAuthLoading = true;
},
[resetPassword.fulfilled]: (state, action) => {
state.isAuthLoading = false;
},
[resetPassword.rejected]: (state) => {
state.isAuthLoading = false;
},
},
});
const { reducer } = authSlice;
const { loading, idle, updateAuthUser, removeAuthUser } = authSlice.actions;
export {
loading as authLoading,
idle as authIdle,
updateAuthUser,
removeAuthUser,
// thunks
checkAuth,
login,
logout,
reqOTPHandler,
signup,
reqResetHandler,
resetPassword,
};
export default reducer;
appSlice.js
import { createSlice } from "#reduxjs/toolkit";
import { checkAuth, logout } from "../authentication/authenticationSlice";
const initialState = {
// Global App loading state
isInitializing: true,
isAppLoading: false,
currentBranch: null,
currentRole: null,
};
const appSlice = createSlice({
name: "app",
initialState,
reducers: {
appInitializing: (state) => {
state.isInitializing = true;
},
appInitialized: (state) => {
state.isInitializing = false;
},
appLoading: (state) => {
state.isAppLoading = true;
},
appFullfilled: (state) => {
state.isAppLoading = false;
},
},
extraReducers: {
// auth
[checkAuth.pending]: (state) => {
state.isInitializing = true;
},
[checkAuth.fulfilled]: (state) => {
state.isInitializing = false;
},
[checkAuth.rejected]: (state) => {
state.isInitializing = false;
},
[logout.pending]: (state) => {
state.isAppLoading = true;
},
[logout.fulfilled]: (state) => {
state.isAppLoading = false;
},
[logout.rejected]: (state) => {
state.isAppLoading = false;
},
},
});
const { reducer } = appSlice;
export const { appInitializing, appInitialized, appLoading, appFullfilled } =
appSlice.actions;
export default reducer;
Don't use the extraReducers object notation - that one will be deprecated soon as it doesn't work with TypeScript - and has this exact problem. If you use the builder notation instead, that will work.
extraReducers: builder => {
// auth
builder.addCase(checkAuth.pending. (state) => {
state.isInitializing = true;
});
...
}
I have a issue with fetching json from server. When I'm trying to getState() from store there's "undefined" value. I'm using "redux-thunk". Help!
Store and state:
const store = createStore(reducer, applyMiddleware(thunk));
const initialState = {
datas: [],
loading: false
};
Reducer:
function reducer(state = initialState, action) {
switch(action.type){
case FETCH_DATA_BEGIN:
return {
...state,
loading: true
};
case FETCH_DATA_SUCCESS:
return {
...state,
datas: action.payload.TableData
};
default:
return state;
};
}
Actions:
function fetchData() {
return dispatch => {
dispatch(fetchDataBegin());
return fetch("http://www.blblblblb.com")
.then(res => res.json())
.then(json => {
dispatch(fetchDataSuccess(json.TableData));
return json.TableData;
})
};
}
const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
const fetchDataSuccess = TableData => ({
type: FETCH_DATA_SUCCESS,
payload: {TableData}
});
const FETCH_DATA_BEGIN = 'FETCH_DATA_BEGIN';
const fetchDataBegin = () => ({
type: FETCH_DATA_BEGIN
});
Thanks!