Redux Toolkit caseReducers skip prepare callback in reducer function - javascript

I have a reducer for adding comment as follow
addComment: {
reducer: (state,action)=>{
const { newId, comment } = action.payload
console.log(newId)
// functions that create an new comment entry with the newID and update state
},
prepare: (input) =>{
return {
payload: {
...input,
newId: nanoid(),
}
}
}
If I call dispatch within my app as follow, the console.log shows the id generated by nanoid(). So far so good.
dispatch(addComment({comment: comment}))
However if I call the reducer within another reducer using the caseReducers, console.log returns undefined
commentSlice.caseReducers.addComment(state,{
payload: {
comment : comment
}
})
How do I get the prepare callback to run when using caseReducers.

Based on the docs it looks like if you are to invoke using the caseReducer way, you need to include the "type":
const slice = createSlice({
name: 'test',
initialState: 0,
reducers: {
increment: (state, action: PayloadAction<number>) => state + action.payload,
},
})
// now available:
slice.actions.increment(2)
// also available:
slice.caseReducers.increment(0, { type: 'increment', payload: 5 })

Related

Updating array in reducer

i am trying to update state in reducer with a array of objects. Following is what i am doing.
Action.js file
export const apiSuccess = (data, forCust) => {
return {
type: ACTIONS.validateSuccess,
data: data,
forCust
};
};
export const apiFailed = (error, forCust) => {
console.log('pandetail failed', error)
return {
type: ACTIONS.ValidateFailed,
data: error?.details?.data,
forCust
};
};
here forCust can be customer or admin
Here is the reducer
const initialState = {
otherDetails: {},
individualDetails: [{ forCust: 'customer' }, { forCust: 'admin' }],
};
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
case ACTIONS.validateSuccess:
console.log('Action in reducer', action)
debugger;
return {
...state,
otherDetails: action?.data,
individualDetails: state.individualDetails.map((item) => {
if(item.forCust === action.forCust){
item = action
}
return item
}),
}
case ACTIONS.validateFailed:
return {
...state,
otherDetails: action?.data,
individualDetails: state.individualDetails.map((item) => {
if (item.forCust === action.forCust) {
item = action
}
return item
}),
}
default:
return state;
}
};
export default reducer;
Now, when action is dispatched it dispatches with success and other parameter which is forCust to check if the dispatched action is for admin or customer. Now i have two fields in the same component but different parameters which calls the action(admin/customer). If the validateSuccess with customer is called i am trying to update customer details in individualDetails array and if admin is called i am trying to update only the admin state array object.
Now, what happens is that i am not able to maintain the old state ie when customer changes and admin details is there, it updated the whole array, not only the customer.
Also if somehow one of them get failed, failed action is dispatched and then update the array corresponding to the forCust field.
any help will be appreciated.

Redux state updates two seperate objects

I have influencer data object. This object is beeing pulled from database with action FETCH_INFLUENCER and put inside two different objects: influencer and formInfluencer in redux store. And then I have action SET_INFLUENCER that is supposed to create new instance of the state and update influencer object in redux. For some reason though it updates both influencer and formInfluencer. I really struggle with finding answer here since I think I did everything to prevent pointing of two different variables to the same object and still it happens.
reducer:
case 'FETCH_INFLUENCER_FULFILLED':
return { ...state, fetching: false, fetched: true, influencer: action.payload.data, formInfluencer: Object.assign([], action.payload.data) }
case 'SET_INFLUENCER':
return { ...state, influencer: action.payload }
actions:
export function fetchInfluencer(id) {
return {
type: "FETCH_INFLUENCER",
payload: axios.get('/api/influencer/' + id, {headers: {Authorization: 'Bearer ' + localStorage.getItem('token')}})
}
}
export function setInfluencer(influencer) {
return {
type: "SET_INFLUENCER",
payload: influencer
}
}
dispatch:
handleUserChange(e) {
let influencer = [...this.props.influencer]
influencer[0].user[e.target.name] = e.target.value;
this.props.dispatch(setInfluencer(influencer))
}
mapping state to props:
const mapStateToProps = (state) => {
return {
influencer: state.influencers.influencer,
formInfluencer: state.influencers.formInfluencer
}
}
export default connect(mapStateToProps)(InfluencerDetails)
If You have any idea why this could be happening I would be happy to hear the answer.
You shouldn't mutate state (if you don't mutate it, then it is no problem that you have multiple variables pointing to the same object).
Instead of:
handleUserChange(e) {
let influencer = [...this.props.influencer]
influencer[0].user[e.target.name] = e.target.value;
this.props.dispatch(setInfluencer(influencer))
}
You should do a bit more work:
handleUserChange(e) {
const newUser = {
...this.props.influencer[0].user,
[e.target.name]: e.target.value
};
const newInfluencer = {
...this.props.influencer[0],
user: newUser
};
const newInfluencers = [...this.props.influencer];
newInfluencers[0] = newInfluencer;
this.props.dispatch(setInfluencer(newInfluencers));
}

Replace using Date.now() inside a reducer

Documentation said i should avoid state mutation by using new Date, etc inside reducers. Help me please with advice how should it be done.
Action:
const RECEIVE_PRICES = 'RECEIVE_PRICES';
function receivePrices(prices) {
return {
type: RECEIVE_PRICES,
receivedAt: Date.now(),
prices,
};
}
REDUCER:
...
case RECEIVE_PRICES: {
let { prices } = action;
prices = prices.map((p) => {
const baseQuote = p.symbol.split('/');
return { ...p, baseCurrency: baseQuote[0], quoteCurrency: baseQuote[1] };
});
prices.sort(
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
);
return {
...state,
prices,
pricesLoading: false,
pricesError: null,
};
}
default:
return state;
}
In Redux, all side-effects (not just api calls) should take place inside Action Creators. You should move this logic into the action creator and have the caller pass the necessary parameters.

How to modify object property in redux?

Trying to create a really simple redux todo, almost there but got stuck on one thing.
export const completeTodo = (todo) => ({
type: 'COMPLETE_TODO',
data: {
name: todo,
complete: !todo.complete
}
})
however, struggling to get the reducer working as I can't work out how to determine the exact object im working on
reducer:
case 'COMPLETE_TODO': {
const chore = { ...state.chores, complete: action.data.complete}
return { ...state.chores, chore };
}
and initialState is:
const initialState = {
chores: [{name: 'cleaning', complete: false}]
}
obviously when i click my button is should be wired up so it can change the complete boolean to the opposite but only for that one todo
Since you have an array you need to replace it with a new one
case 'COMPLETE_TODO': {
return {
...state,
chores: state.chores.map(chore =>
chore.name === action.data.name
? {...chore, complete: true /* or !chore.complete if you want toggle like behaviour*/}
: chore)
};
}
and in your action creator
export const completeTodo = (todo) => ({
type: 'COMPLETE_TODO',
data: {
name: todo.name // assumning names are unique
}
})

Updating object inside redux state throws an error

I'm trying to update array inside object in my reducer.
const initialState = {
group: {
name: "",
date: "",
description: "",
users: [],
posts: []
},
morePosts: false,
groups: []
};
export function groups(state = initialState, action) {
switch (action.type) {
.......
case REQUEST_MORE_POSTS:
{
return {
...state,
group:{
...state.group,
posts: [
...state.group.posts,
...action.payload.posts
]
},
morePosts: action.payload.morePosts
}
}
case ADD_NEW_POST:
{
return {
...state,
group:{
...state.group,
posts: [
action.payload,
...state.group.posts
]
}
}
}
........
default:
return state;
}
}
Unfortunately in both cases I get an error:
It works when I extract posts out of my group object but I need it inside.
I can't figure out what I've done wrong here. Can someone point me to the right direction?
Here is an action creator for adding new post.
export function addPost(url, payload) {
return function(dispatch) {
axios.post(url + "php/addPostGroup.php", {payload}).then(response => {
dispatch({
type: ADD_NEW_POST,
payload: response.data.post
})
})
}
}
response.data.post is a simple object.
I've added console.log() before dispatch. This is how my response looks like:
Alright I solved it. Before fetching any data my state.group.posts array was for some reason treated as undefined. I had to manually declare it as empty array using
var posts = state.group.posts != undefined ? state.group.posts:[];

Categories