I have an initial state like this.
const initialState = {
loading: false,
products: []
}
For the get products, I use thunk and I fetch datas from an API. loading field describes status of API call. If loading is true, i show progress in my component.
export const getProducts = createAsyncThunk('product/getProducts ',
async (payload, { rejectWithValue }) => {
try {
//fetch from somewhere
}
catch (e) {
return rejectWithValue(e.message)
}
}
)
const productSlice = createSlice({
name: "product",
initialState,
reducers: {
setLoading: (state, action) => {
state.loading = action.payload;
}
},
})
In a component first dispatch for loading and then getProducts.
// In some component
dispatch(setLoading(true))
dispatch(getProducts())
My question is, can I only call for getProducts and update loading state inside that function?
//In some component
dispatch(getProducts())
Yes you can. This is very simple
Update your createSlice section like this
const productSlice = createSlice({
name: "product",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getProducts.pending, (state) => {
state.loading = true;
});
builder.addCase(getProducts.fulfilled, (state) => {
state.loading = false;
});
},
})
You used createAsyncThunk and it is built in already to handle pending/fulfilled/rejected state with extraReducers in createSlice.
You can also handle the rejected state if fetch fail.
const productSlice = createSlice({
name: 'product',
initialState,
extraReducers: (builder) => {
builder.addCase(getProducts.pending, (state, action) => {
state.isLoading = true;
})
builder.addCase(getProducts.fulfilled, (state, action) => {
state.isLoading = true;
state.products = action.payload
})
builder.addCase(getProducts.rejected, (state, action) => {
state.isLoading = false;
})
},
})
To use it, will be enough to simple call dispatch(getProducts()) and extraReducers with thunk take care of the rest.
Related
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;
res in then gets me an object but still can t update it redux state
i created account the when it returns me the value res .i can console it but i can t save it in the redux state
const initialState = {
user: {},
};
export const Auth_Slice = createSlice({
name: "auth",
initialState,
reducers: {
loginWithEmail: async (state, action) => {
await createUserWithEmailAndPassword(
auth,
action.payload.email,
action.payload.password
).then((res) => {
state.user = res;
console.log(res.user);
});
},
},
});
you need to create async actions and use extraReducers in create slice.
export const createUserWithEmailAndPassword = createAsyncThunk(
"createUserWithEmailAndPassword",
async (payload) => {
const response = await createUserWithEmailAndPassword(
auth,
payload.email,
payload.password
);
return response;
}
);
const initialState = {
status: "",
user: {},
};
export const Auth_Slice = createSlice({
name: "auth",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(createUserWithEmailAndPassword.pending, (state) => {
state.status = "loading";
})
.addCase(createUserWithEmailAndPassword.fulfilled, (state, action) => {
state.status = "idle";
state.user = action.payload?.user ?? {};
});
}
})
;
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
I want to know if it's possible (or a good practice) to call dispatch(someDumbAction()) from an extraReducer.
For example, I have a setData() action in reducers object from createSlice.
I want to call setData() directly in my component. But I want to call it too in a extraReducer listener, in order to reuse the reducer logic, like below:
// Thunk Action
export const getData = createAsyncThunk('data/getData', async (params) => {
return await api.get({ params })
})
// Slice creation
const slice = createSlice({
name: 'data',
initialState: [],
reducers: {
setData: (state, { payload }) => {
state.push(payload);
})
},
extraReducers: (builder: any) => {
builder.addCase(getData.pending, (state) => {
//...
})
builder.addCase(getData.rejected, (state) => {
//...
})
builder.addCase(getData.fulfilled, (state, { payload }) => {
// Here I want to dispatch `setData` action, in order to reuse that logic
// dispatch(setData(payload));
})
},
})
// In any component: dispatch(setData([...]);
No. Reducers can never dispatch actions:
https://redux.js.org/style-guide/style-guide#reducers-must-not-have-side-effects
However, it looks like what you're really asking for here is the ability to run the same state update logic steps in multiple situations.
You could define the logic as a standalone reducer function, and reuse it in both cases:
function addItem(state, action) {
state.push(action.payload);
}
const slice = createSlice({
name: 'data',
initialState: [],
reducers: {
setData: addItem
},
extraReducers: (builder: any) => {
builder.addCase(getData.pending, (state) => {
//...
})
builder.addCase(getData.rejected, (state) => {
//...
})
builder.addCase(getData.fulfilled, addItem)
},
})
You could also define the function as part of reducers, and then reference it inside of the extraReducers handler:
const slice = createSlice({
name: 'data',
initialState: [],
reducers: {
setData: (state, { payload }) => {
state.push(payload);
})
},
extraReducers: (builder: any) => {
builder.addCase(getData.pending, (state) => {
//...
})
builder.addCase(getData.rejected, (state) => {
//...
})
builder.addCase(getData.fulfilled, (state, action) => {
slice.caseReducers.setData(state, action);
})
},
})
Here is an example:
const user = createSlice({
name: 'user',
initialState: { name: '', age: 20 },
reducers: {
setUserName: (state, action) => {
state.name = action.payload // mutate the state all you want with immer
}
},
// "map object API"
extraReducers: {
[counter.actions.increment]: (state, action) => {
state.age += 1
}
}
})
Can I get access to the counter state?
Let's say I want to increment age only when the counter is 30. Otherwise, I would need to listen when the count is changing in useEffect hook and dispatch some action that will handle age increment (?).
In other words, what's the best way to compute the slice of state based on the current global state using redux-toolkit?
This is covered in the Redux FAQ entry on sharing state between reducers.
Pasting the key points:
If a reducer needs to know data from another slice of state, the state tree shape may need to be reorganized so that a single reducer is handling more of the data.
You may need to write some custom functions for handling some of these actions. This may require replacing combineReducers with your own top-level reducer function. You can also use a utility such as reduce-reducers to run combineReducers to handle most actions, but also run a more specialized reducer for specific actions that cross state slices.
Async action creators such as redux-thunk have access to the entire state through getState(). An action creator can retrieve additional data from the state and put it in an action, so that each reducer has enough information to update its own state slice.
I think you could use thunkAPI and extrareducers like below, assuming that you have an auth slice which has the current user object:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import ordersService from "./ordersService";
const initialState = {
orders: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
//get orders
export const getOrders = createAsyncThunk(
"orders/getOrders",
async (__, thunkAPI) => {
try {
const userId = thunkAPI.getState().auth.user.id
return await ordersService.getOrders(userId);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const ordersSlice = createSlice({
name: "orders",
initialState,
reducers: {
reset: (state) => {
state.isError = false;
state.isSuccess = false;
state.isLoading = false;
state.message = "";
},
},
extraReducers: (builder) => {
builder
.addCase(getOrders.pending, (state) => {
state.isLoading = true;
})
.addCase(getOrders.fulfilled, (state, action) => {
state.isLoading = false;
state.isSuccess = true;
state.orders = action.payload;
})
.addCase(getOrders.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
state.orders = [];
});
},
});
export const { reset, setOrder } = ordersSlice.actions;
export default ordersSlice.reducer;