Redux toolkit state is not updating after API response - reactjs

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

Related

Data fetching with Redux RTK - watching states

I am very new to RTK, so I am trying to create a store and a slicer.
At first, at least I want to fetch some data from an API so when it start loading and after being succeed, I know the state of it.
Here I am creatinf the slicer:
const initialState: PlayerState = {
players: [],
status: 'idle'
};
export const getPlayers = createAsyncThunk('players/getPlayers', async () => {
const response = await axios.get(
'https://6360055fca0fe3c21aaacc04.mockapi.io/player'
);
return response.data;
});
const playerSlice = createSlice({
name: 'players',
initialState,
reducers: {
addPlayer: (state, action: PayloadAction<IPlayerProps>) => {
console.log('done');
state.players.push(action.payload);
}
},
extraReducers: {
[getPlayers.pending]: (state, action) => {
console.log('loading');
state.status = 'loading';
},
[getPlayers.fulfilled]: (state, action) => {
console.log('succeeded');
state.status = 'succeeded';
state.players = state.players.concat(action.payload);
}
}
});
export const { addPlayer } = playerSlice.actions;
export const selectPlayers = (state: RootState) => state.players.payload;
And here I am trying to connect it to the store:
//#ts-nocheck
import { configureStore } from '#reduxjs/toolkit'
import { addPlayer } from './playerSlice'
export const store = configureStore({
reducer: {
players: addPlayer,
},
})
export type RootState = ReturnType<typeof store.getState>;
So, after that I have a page with a button, so when I click it I try to dispatch something out of it with no luck unfortunately:
const NextPage = () => {
const dispatch = useDispatch();
return (
<ButtonNext
onClick={() => {
dispatch(addPlayer);
}}
text="< Back"
/>
);
};
export default NextPage;
Any help would be appreciated! :)
The are several issues in your code
First fix your createAsyncThunk
export const getPlayers = createAsyncThunk('players/getPlayers'
async (_unusedArgs, _thunkApi) => {
const response = await fetch('http://localhost:3000/players')
return response.json()
}
)
Your slice should look like this, note the builder callbacks for the cases:
export const playerSlice = createSlice({
name: "players",
initialState,
reducers: {
addPlayer: (state, action) => {
console.log("done");
state.players.push(action.payload);
}
},
extraReducers: (builder) => {
builder.addCase(getPlayers.fulfilled, (state, action) => {
console.log(action.payload);
state.players = action.payload;
state.status = "idle";
});
builder.addCase(getPlayers.pending, (state, action) => {
console.log("loading");
state.status = "loading";
});
}
});
export default playerSlice.reducer;
Call it inside the anonymous fn
<ButtonNext
onClick={() => {
dispatch(getPlayers()); // call with no arguments.
}}
text="< Back"
/>
And I also think that your root reducer in store is not right
import playerSlice from './playerSlice' // defaulted export
export const store = configureStore({
reducer: {
players: playerSlice,
},
})
Please check this sandbox with working example: https://codesandbox.io/s/redux-toolkit-basic-players-w-pokemons-6wmjm0?file=/src/features/playerSlice.js:409-995

When i am trying to call the api using thunk , i am getting undefined

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
})
};

How can I change state of another reducer from extra reducers add cases

I am trying to create a notification Component.
My notification Component is at root level and when ever user tries to login the process of the the async function is relayed to him i.e. pending fulfilled or rejected.
The Problem is that I don't know how to call notification reducer from userSlice or even if its possible is this a good way or not.
User Slice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const initialUserState = {
currentUser:null
}
export const getUser = createAsyncThunk(
'user/getUser',
async (endpoint, data) => {
return(
await axios.post(endpoint, data)
.then(res =>{
return res.data.user
})
.catch(error =>{
throw Error(error.response.data)
})
)
}
)
const userSlice = createSlice({
name: 'user',
initialState: initialUserState,
reducers:{
currentUser(state, action){
state.currentUser = action.payload
}
},
extraReducers:
(builder) => {
builder.addCase(getUser.pending, ()=>{
console.log("authing")
})
builder.addCase(getUser.fulfilled, (state, action)=>{
state.currentUser = action.payload
console.log("fulfilled")
})
builder.addCase(getUser.rejected, (state, action)=>{
console.log("failed")
alert(action.error.message)
})
}
})
export const userActions = userSlice.actions;
export default userSlice.reducer;
notificationSlice
import React from 'react'
import { useSelector } from 'react-redux'
function Notification() {
const toast = useSelector(state => state.notification)
console.log(toast)
return (
toast.active &&
<div className="notification" style={{backgroundColor:toast.backgroundColor}} >
{toast.message}
</div>
)
}
export default Notification
I want to change notification state when ever one of the extra reducer in userSlice is called
I think you are thinking about this almost exactly backwards. What you want is NOT to "call notification reducer from userSlice," but to LISTEN for userSlice actions in a notificationSlice.
I have done something like the following, which I think would work well for you:
import { createEntityAdapter, createSlice, isAnyOf } from '#reduxjs/toolkit'
const notificationsAdapter = createEntityAdapter()
const initialState = notificationsAdapter.getInitialState({
error: null,
success: null,
})
const notificationsSlice = createSlice({
name: 'notifications',
initialState,
reducers: {
clearNotifications: state => {
state.error = null
state.success = null
},
setError: (state, action) => {
state.success = null
state.error = action.payload
},
setSuccess: (state, action) => {
state.success = action.payload
state.error = null
},
},
extraReducers: builder => {
builder
.addMatcher(
isAnyOf(
getUser.fulfilled,
),
(state, action) => {
state.error = null
state.success = action.payload.message
}
)
.addMatcher(
isAnyOf(
getUser.rejected
// can add as many imported actions
// as you like to these
),
(state, action) => {
state.error = action?.payload
state.success = null
}
)
// reset all messages on pending
.addMatcher(
isAnyOf(
getUser.pending
),
(state, action) => {
state.error = null
state.success = null
}
)
},
})
export const { clearNotifications, setError, setSuccess } = notificationsSlice.actions
export default notificationsSlice.reducer
export const getErrorMsg = state => state.notifications.error
export const getSuccessMsg = state => state.notifications.success
Having added the above, you can now create a notification component that listens for
const error = useSelector(getErrorMsg)
const success = useSelector(getSuccessMsg)
and shows the messages accordingly.
Caveat:
My notificationSlice code assumes that when an action completes, there will exist a "message" object on the success payload. So, on my async thunks, if my api does not return this I must add this explicitly to the result.

Adding function in Slice for redux

Please help me how I can introduce new function like getOrdersByCustomer in ordersSlice. I have provided full code of ordersSlice below. Please tell me what is extraReducers and how it works.
import { createSlice, createAsyncThunk, createEntityAdapter } from '#reduxjs/toolkit';
import axios from 'axios';
export const getOrders = createAsyncThunk('eCommerceApp/orders/getOrders', async () => {
const response = await axios.get('/api/e-commerce-app/orders');
const data = await response.data;
return data;
});
export const removeOrders = createAsyncThunk(
'eCommerceApp/orders/removeOrders',
async (orderIds, { dispatch, getState }) => {
await axios.post('/api/e-commerce-app/remove-orders', { orderIds });
return orderIds;
}
);
const ordersAdapter = createEntityAdapter({});
export const { selectAll: selectOrders, selectById: selectOrderById } = ordersAdapter.getSelectors(
state => state.eCommerceApp.orders
);
const ordersSlice = createSlice({
name: 'eCommerceApp/orders',
initialState: ordersAdapter.getInitialState({
searchText: ''
}),
reducers: {
setOrdersSearchText: {
reducer: (state, action) => {
state.searchText = action.payload;
},
prepare: event => ({ payload: event.target.value || '' })
}
},
extraReducers: {
[getOrders.fulfilled]: ordersAdapter.setAll,
[removeOrders.fulfilled]: (state, action) => ordersAdapter.removeMany(state, action.payload)
}
});
export const { setOrdersSearchText } = ordersSlice.actions;
export default ordersSlice.reducer;
In Addition
Also can you please tell me what I will do with this following code for my custom function getOrdersByCustomer.
export const { selectAll: selectOrders, selectById: selectOrderById } = ordersAdapter.getSelectors(
state => state.eCommerceApp.orders
);
because, in my component I have used like
const orders = useSelector(selectOrders);
You can introduce new (async) functions as you already have (I used the customerId as part of the url -> you could access it through the params in your backend):
export const getOrdersByCustomer = createAsyncThunk('eCommerceApp/orders/getOrdersByCustomer', async (customerId) => {
const response = await axios.get(`/api/e-commerce-app/orders/${customerId}`);
const data = await response.data;
return data;
});
Then you can handle the response in your extraReducer:
extraReducers: {
[getOrders.fulfilled]: ordersAdapter.setAll,
[removeOrders.fulfilled]: (state, action) => ordersAdapter.removeMany(state, action.payload),
[getOrdersByCustomer.fulfilled]: (state, action) =>
// set your state to action.payload
}
The extraReducers handle actions like async thunks. The createAsyncThunk function return 3 possible states (along with other things): pending, rejected or fulfilled. In your case you only handle the fulfilled response. You could also set your state with the other two options (in your case [getOrdersByCustomer.pending] or [getOrdersByCustomer.rejected]

How to use redux toolkit + redux thunks to create multiple async actions? [duplicate]

I have come across Redux Toolkit (RTK) and wanting to implement further functionality it provides. My application dispatches to reducers slices created via the createSlice({}) (see createSlice api docs)
This so far works brilliantly. I can easily use the built in dispatch(action) and useSelector(selector) to dispatch the actions and receive/react to the state changes well in my components.
I would like to use an async call from axios to fetch data from the API and update the store as the request is A) started B) completed.
I have seen redux-thunk and it seems as though it is designed entirely for this purpose, but the new RTK does not seem to support it within a createSlice() following general googling.
Is the above the current state of implementing thunk with slices?
I have seen in the docs that you can add extraReducers to the slice but unsure if this means I could create more traditional reducers that use thunk and have the slice implement them?
Overall, it is misleading as the RTK docs show you can use thunk, but doesn't seem to mention it not being accessible via the new slices api.
Example from Redux Tool Kit Middleware
const store = configureStore({
reducer: rootReducer,
middleware: [thunk, logger]
})
My code for a slice showing where an async call would fail and some other example reducers that do work.
import { getAxiosInstance } from '../../conf/index';
export const slice = createSlice({
name: 'bundles',
initialState: {
bundles: [],
selectedBundle: null,
page: {
page: 0,
totalElements: 0,
size: 20,
totalPages: 0
},
myAsyncResponse: null
},
reducers: {
//Update the state with the new bundles and the Spring Page object.
recievedBundlesFromAPI: (state, bundles) => {
console.log('Getting bundles...');
const springPage = bundles.payload.pageable;
state.bundles = bundles.payload.content;
state.page = {
page: springPage.pageNumber,
size: springPage.pageSize,
totalElements: bundles.payload.totalElements,
totalPages: bundles.payload.totalPages
};
},
//The Bundle selected by the user.
setSelectedBundle: (state, bundle) => {
console.log(`Selected ${bundle} `);
state.selectedBundle = bundle;
},
//I WANT TO USE / DO AN ASYNC FUNCTION HERE...THIS FAILS.
myAsyncInSlice: (state) => {
getAxiosInstance()
.get('/')
.then((ok) => {
state.myAsyncResponse = ok.data;
})
.catch((err) => {
state.myAsyncResponse = 'ERROR';
});
}
}
});
export const selectBundles = (state) => state.bundles.bundles;
export const selectedBundle = (state) => state.bundles.selectBundle;
export const selectPage = (state) => state.bundles.page;
export const { recievedBundlesFromAPI, setSelectedBundle, myAsyncInSlice } = slice.actions;
export default slice.reducer;
My store setup (store config).
import { configureStore } from '#reduxjs/toolkit';
import thunk from 'redux-thunk';
import bundlesReducer from '../slices/bundles-slice';
import servicesReducer from '../slices/services-slice';
import menuReducer from '../slices/menu-slice';
import mySliceReducer from '../slices/my-slice';
const store = configureStore({
reducer: {
bundles: bundlesReducer,
services: servicesReducer,
menu: menuReducer,
redirect: mySliceReducer
}
});
export default store;
I'm a Redux maintainer and creator of Redux Toolkit.
FWIW, nothing about making async calls with Redux changes with Redux Toolkit.
You'd still use an async middleware (typically redux-thunk), fetch data, and dispatch actions with the results.
As of Redux Toolkit 1.3, we do have a helper method called createAsyncThunk that generates the action creators and does request lifecycle action dispatching for you, but it's still the same standard process.
This sample code from the docs sums up the usage;
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
})
},
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
See the Redux Toolkit "Usage Guide: Async Logic and Data Fetching" docs page for some additional info on this topic.
Hopefully that points you in the right direction!
You can use createAsyncThunk to create thunk action, which can be trigger using dispatch
teamSlice.ts
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
const axios = require("axios");
export const fetchPlayerList = createAsyncThunk(
"team/playerListLoading",
(teamId: string) =>
axios
.get(`https://api.opendota.com/api/teams/${teamId}/players`)
.then((response) => response.data)
.catch((error) => error)
);
const teamInitialState = {
playerList: {
status: "idle",
data: {},
error: {},
},
};
const teamSlice = createSlice({
name: "user",
initialState: teamInitialState,
reducers: {},
extraReducers: {
[fetchPlayerList.pending.type]: (state, action) => {
state.playerList = {
status: "loading",
data: {},
error: {},
};
},
[fetchPlayerList.fulfilled.type]: (state, action) => {
state.playerList = {
status: "idle",
data: action.payload,
error: {},
};
},
[fetchPlayerList.rejected.type]: (state, action) => {
state.playerList = {
status: "idle",
data: {},
error: action.payload,
};
},
},
});
export default teamSlice;
Team.tsx component
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchPlayerList } from "./teamSlice";
const Team = (props) => {
const dispatch = useDispatch();
const playerList = useSelector((state: any) => state.team.playerList);
return (
<div>
<button
onClick={() => {
dispatch(fetchPlayerList("1838315"));
}}
>
Fetch Team players
</button>
<p>API status {playerList.status}</p>
<div>
{playerList.status !== "loading" &&
playerList.data.length &&
playerList.data.map((player) => (
<div style={{ display: "flex" }}>
<p>Name: {player.name}</p>
<p>Games Played: {player.games_played}</p>
</div>
))}
</div>
</div>
);
};
export default Team;
Use redux-toolkit v1.3.0-alpha.8
Try this
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
export const myAsyncInSlice = createAsyncThunk('bundles/myAsyncInSlice', () =>
getAxiosInstance()
.get('/')
.then(ok => ok.data)
.catch(err => err),
);
const usersSlice = createSlice({
name: 'bundles',
initialState: {
bundles: [],
selectedBundle: null,
page: {
page: 0,
totalElements: 0,
size: 20,
totalPages: 0,
},
myAsyncResponse: null,
myAsyncResponseError: null,
},
reducers: {
// add your non-async reducers here
},
extraReducers: {
// you can mutate state directly, since it is using immer behind the scenes
[myAsyncInSlice.fulfilled]: (state, action) => {
state.myAsyncResponse = action.payload;
},
[myAsyncInSlice.rejected]: (state, action) => {
state.myAsyncResponseError = action.payload;
},
},
});

Resources