I try to use 'redux toolkit' to create async function for my React Native app. below are my createAsyncThunk function:
export const signin = createAsyncThunk(
"signin",
async (dispatch, getState) =>{
return await axios.post(localhost+'/api/user_auth/', {
email:'abc#example.com',
password:'password'
}).then(res=> console.log(res.data))
}
)
export const userSlice = createSlice({
name: 'user',
initialState: {
user:{},
status:null
},
extraReducers:{
[signin.pending]: (state, action)=>{
state.status = 'loading...'
},
[signin.fulfilled]: (state, action)=>{
state.status = 'success'
state.user = action.payload
},
[signin.rejected]: (state, action)=>{
state.status = 'failed'
},
},
reducers: {
},
})
when running the function everything seems to work, the console.log inside the then() will return the correct data. But when I log out the state.user I will get:
{"status": "success", "user": {}}
How should I return the response data?
If I want only the specific part of the response data like response.data, how can I filter that to return to reducer?
Update01
I just test this:
export const signin = createAsyncThunk(
"signin",
async (dispatch, getState) =>{
const res = await axios.post(localhost+'/api/user_auth/', {
email:'abc#example.com',
password:'password'
})
console.log( await res.data)
return await res.json()//<-- give status "failed"
}
)
And I when I logged state.user, I will get:
{"status": "failed", "user": {}}
It seems like the toolkit is very particular on the await format.
Related
I am trying to make a POST request to an endpoint with React. On fulfilled state of my builder.addCase reducer, my data is generated or return back to me when fulfilled, but on rejected state of my builder.addCase reducer, checking my Redux Dev Tools, no rejected state is found even though I have an error. Rather the error message that ought to be in rejected state is found or populated to fulfilled state. Checking Redux Dev Tools I can only find pending and fulfilled state, rejected state is nowhere to be found.
Here is my code:
export const userRegisterAction = createAsyncThunk(
"users/register",
async (user: User, { rejectWithValue }) => {
try {
const response = await axios.post(
"http://localhost:5000/api/users/register",
user,
);
return response.data;
} catch (error) {
return rejectWithValue(error);
}
}
);
Here is my slice:
const usersSlices = createSlice({
name: "users",
initialState: {
userAuth: "login",
registered: {},
loading: false,
Error: ""
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(userRegisterAction.pending, (state) => {
state.loading = true
})
builder.addCase(userRegisterAction.fulfilled, (state, { payload }) => {
state.registered = payload
})
builder.addCase(userRegisterAction.rejected, (state) => {
state.loading = false;
})
}
});
export default usersSlices.reducer;
Here is where I dispatch my action.
const dispatch = useAppDispatch();
export interface User {
firstName : string;
password : string;
lastName: string;
email : string;
}
export const Signup = (): JSX.Element => {
const [passwordFieldType, setPasswordFieldType] = useState<boolean>(false);
const [user, setUser] = useState<User>({
firstName: "",
lastName: "",
email: "",
password: ""
});
const dispatch = useAppDispatch();
const handleInputChange = (e: any) => {
const name = e.target.name;
const value = e.target.value;
setUser({
...user,
[name]: value.trim()
});
}
const handleInputSubmit = async (event: any) => {
event.preventDefault();
const { firstName, lastName, email, password } = user;
if (!firstName || !lastName || !email || !password) {
return toast.error(
"Please, fill up all inputs !!!",
{
toastId: "fill_inputs",
position: toast.POSITION.TOP_CENTER,
autoClose: 1000,
}
);
}
const response = await dispatch(userRegisterAction({
firstName,
email,
password,
lastName
}))
}
}
I have tried everything I could, but to no avail. I've checked online too, no related help or answer to the issue.
I encountered the same challenge a few minutes ago. For me, it was because I did not add "return" before my "rejectWithValue"; which prevented my "rejected" from working because the function is a promise which has three states. And whenever you are working with a promise, always return your response.
For you, you need to parse "error.response.data" instead of only "error" in your reject function and update the state "Error" with the rejected action. See the implementation below.
The previous implementation of the userRegisterAction function
export const userRegisterAction = createAsyncThunk(
"users/register",
async (user: User, { rejectWithValue }) => {
try {
const response = await axios.post(
"http://localhost:5000/api/users/register",
user,
);
return response.data;
} catch (error) {
return rejectWithValue(error);
}
}
);
The new implementation of the userRegisterAction function
export const userRegisterAction = createAsyncThunk(
"users/register",
async (user: User, { rejectWithValue }) => {
try {
const response = await axios.post(
"http://localhost:5000/api/users/register",
user,
);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data); //This carries the response you are receiving from the server
}
}
);
Previous implementation of the usersSlice
const usersSlices = createSlice({
name: "users",
initialState: {
userAuth: "login",
registered: {},
loading: false,
Error: ""
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(userRegisterAction.pending, (state) => {
state.loading = true
})
builder.addCase(userRegisterAction.fulfilled, (state, { payload }) => {
state.registered = payload
})
builder.addCase(userRegisterAction.rejected, (state) => {
state.loading = false;
})
}
});
export default usersSlices.reducer;
New implementation of usersSlice
const usersSlices = createSlice({
name: "users",
initialState: {
userAuth: "login",
registered: {},
loading: false,
Error: ""
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(userRegisterAction.pending, (state) => {
state.loading = true
})
builder.addCase(userRegisterAction.fulfilled, (state, { payload }) => {
state.registered = payload
})
builder.addCase(userRegisterAction.rejected, (state) => {
state.loading = false;
state.Error = payload; // Note: I updated the state here
})
}
});
export default usersSlices.reducer;
I hope this helps you resolve the bug.
I'm trying to achieve automated refetching without using RTK Query.
I.e. im writing my API calls using createAsyncThunk & Axios.
On create(post) or update(patch) request being successful, I would rather just "refetch" data, rather than push my changes into the original data set.
This is how it would be done with RTK Query:https://redux-toolkit.js.org/rtk-query/usage/automated-refetching
By invalidating the data.
A simple solution would be if I could call my "fetchClients" thunk..
Any ideas?
import { createSlice, createAsyncThunk, current } from "#reduxjs/toolkit/";
import axios from "axios";
import { getAxiosHeaders } from "../Api/apiHelper";
const initialState = {
clients: [],
client: null,
status: "idle", //'idle' | 'loading' | 'succeeded' | 'failed'
error: null,
isModalOpen: false,
};
export const fetchClients = createAsyncThunk("clients/fetchClients", async () => {
alert("hello fetch clients");
const response = await axios.get(CLIENTS_URL, { headers });
return response.data.clients;
});
export const addNewClient = createAsyncThunk("clients/addNewClient", async (body) => {
const response = await axios.post(CLIENTS_URL, body, { headers });
return response.data;
});
export const clientsSlice = createSlice({
name: "clients",
initialState,
reducers: {
toggleModal: (state, action) => {
state.isModalOpen = !action.payload;
},
},
},
extraReducers: (builder) => {
builder
.addCase(fetchClients.pending, (state, action) => {
state.status = "loading";
})
.addCase(fetchClients.fulfilled, (state, action) => {
state.status = "success";
state.clients = action.payload;
})
.addCase(fetchClients.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
})
.addCase(addNewClient.fulfilled, (state, action) => {
console.log(state.clients);
// REFETCH DATA i.e. fetchClients()
// state.clients.push(action.payload);
})
},
});
export const { clientAdded, toggleModal, setClient } = clientsSlice.actions;
export default clientsSlice.reducer;
I am new to redux toolkit, My objective is to not let the dashboard to render if the user who is trying to login is not a registered member. I am sending a 500 error from the backend if the username or password is invalid which works fine. The extraReducers is not going to rejected case even if the api response is 500 instead it goes to the fulfilled case.My expectation was that if the api gives an error, it will go to the rejected case. Below is my slice,
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import axios from "axios";
export const currentUser = localStorage.getItem("userInfo");
const initialState = {
currentUser: currentUser
? currentUser
: {
email: "",
password: "",
name: "",
pic: "",
confirmPassword: "",
},
status: null,
isLoggedIn: currentUser ? true : false,
userInfoFromStorage: "",
};
//login action
export const authUser = createAsyncThunk("users/authUser", async (data) => {
const res = await axios.post("http://localhost:3010/api/users/login", data);
return res.data;
});
//register action
export const registerUser = createAsyncThunk(
"users/registerUser",
async (data) => {
try {
const res = await axios.post("http://localhost:3010/api/users", data);
console.log(res);
return res.data;
} catch (error) {
console.log(error);
}
}
);
const userReducer = createSlice({
name: "user", //to identify the slice
initialState,
reducers: {
logoutUser: (state, action) => {
state.isLoggedIn = false;
localStorage.removeItem("userInfo");
},
},
extraReducers: (builder) => {
builder.addCase(authUser.pending, (state) => {
console.log("pending");
state.status = "pending";
});
builder.addCase(authUser.fulfilled, (state, action) => {
state.status = "success";
state.isLoggedIn = true;
state.currentUser = action.payload;
state.userInfoFromStorage = localStorage.setItem(
"userInfo",
JSON.stringify(action.payload)
);
});
builder.addCase(authUser.rejected, (state) => {
console.log("failed")
state.status = "failed";
state.isLoggedIn = false;
});
},
});
// Action creators are generated for each case reducer function
export const { logoutUser } = userReducer.actions;
export default userReducer.reducer;
dispatching action below from loginPage
const submitHandler = async (e) => {
e.preventDefault();
dispatch(authUser({ email: email, password: password }));
// history.push("/myNotes");
};
In your thunk, you are manually handling the error. There is no way that any other code after that could know that something went wrong.
You have three options here:
do not try..catch in your createAsyncThunk call. The error will be cleaned up from non-standard fields and end up in action.error of the rejected action
manually throw something. The error will be cleaned up from non-standard fields and end up in action.error of the rejected action
manually return thunkApi.rejectWithValue(yourError). The error will not be cleaned up and land in action.payload of the rejected action.
What you are doing now (handling the error and returning nothing) is essentially equal to doing return undefined in your function - which will end up as a fulfilled action with undefined as payload.
I am using react with redux tookit to call my api and store my response in the state, but whenever i am calling the async thunk i am getting undefined in response but i am log my response in api, i am getting the expected response, i didn't know as i am beginner in redux, can anybody please help me what i am doing wrong.
below is my memory slice reducer
import { RecentPublishedApi } from "../../components/api/api";
export const fetchMemoryAsync = createAsyncThunk(
"memory/recentPublishedApi",
async (obj) => {
const response =await RecentPublishedApi(obj);
console.log("i am inside the thunk",response)
return response;
}
);
const initialState = {
data: [],
status: "",
};
export const MemorySlice = createSlice({
name: "memory",
initialState,
reducers: {
},
extraReducers: {
[fetchMemoryAsync.pending]: (state) => {
state.status = "loading";
},
[fetchMemoryAsync.fulfilled]: (state, action) => {
console.log("fulfilled")
state.status = "idle";
console.log(action)
state.data=action.payload;
},
[fetchMemoryAsync.rejected]: (state, action) => {
state.status = "failed";
state.error = action.payload;
},
},
});
// export const { addData } = MemorySlice.actions;
export const selectData = (state) => state.memory.data;
export default MemorySlice.reducer;
my code sandbox link-
https://codesandbox.io/s/hidden-snowflake-px34f?file=/src/App.js
You are not returning anything from your RecentPublishedApi. Add a return statement.
Also, that Promise you build there is already a promise, no need to wrap that manually.
import axios from "axios";
export const RecentPublishedApi = async (data) => {
const headers = {
"Content-Type": "application/json",
"X-CSRF-TOKEN": "e7lwcn_OBGJuu2QsIA8auXzsvi9RGlzueRGDDwVsSKU"
};
return axios
.post("https://public.cuebackqa.com/api/timeline/list", data, {
headers: headers
})
};
new to redux toolkit.
Redux reducer not updating the state after API response, if update the state before API call its working fine, described my code in below
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
value: 0,
};
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {
allUsers: (state) => {
state.value = 2; //state is updating here
fetch("http://bss-api.test/api/admin/users")
.then((response) => response.json())
.then((result) => {
state.value = 5; //state is not updating here
})
.catch((e) => {
console.log(e);
});
},
},
});
export const { allUsers } = userSlice.actions;
export default userSlice.reducer;
You must never run any async logic in a reducer:
https://redux.js.org/style-guide/style-guide#reducers-must-not-have-side-effects
Any async logic belongs outside the reducer, such as in a thunk:
https://redux.js.org/usage/writing-logic-thunks
Additionally, all Redux updates are caused by dispatching an action.
We'd recommend using RTK's createAsyncThunk utility to help define and dispatch actions automatically based on the promise from the request:
https://redux.js.org/tutorials/essentials/part-5-async-logic
resolved this problem by understanding createAsyncThunk utility of RTK's
described in code below
import { createSlice, createAsyncThunk} from "#reduxjs/toolkit";
export const getUsers = createAsyncThunk(
'getUsers',
async ({ limit }, { dispatch, getState }) => {
return fetch(
`http://bss-api.test/api/admin/users`
).then((res) => res.json())
}
)
export const userSlice = createSlice({
name:'user',
initialState:{
list:[],
status:null
},
extraReducers: {
[getUsers.pending]: (state, action) => {
state.status = 'loading'
},
[getUsers.fulfilled]: (state, { payload }) => {
state.list = payload
state.status = 'success'
},
[getUsers.rejected]: (state, action) => {
state.status = 'failed'
},
},
});
export default userSlice.reducer