Updating object inside redux state throws an error - javascript

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:[];

Related

Redux Toolkit caseReducers skip prepare callback in reducer function

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

Using spread operator to add new value to key value pair in object

Struggling to figure out how to add a new item to an object from my reducer. This is the payload I am receiving GH
This is the relevant code in my reducer
const initialForm = {
nationality: '',
}
function addName(state= initialForm, action) {
switch(action.type){
case ADD_NEW_NATIONALITY:
console.log('nationality',action.payload.name)
return {
...state,
[action.payload.nationality]: action.payload.value
}
default:
return state
}
}
and my action creators
export const addFullName = newName => dispatch => {
console.log(newName)
axios.get(`https://api.nationalize.io?name=${newName.name}`)
.then(res => {
dispatch({ type: ADD_NEW_NATIONALITY, payload: res.data.country[0].country_id})
})
Please help me figure this out
Your payload is just a plain value (not an object), so you cannot find action.payload.name and action.payload.value
And similarly, you cannot find action.payload.nationality either.
Here is how your action looks like
{ type: ADD_NEW_NATIONALITY, payload: 1} //`1` is country_id
To set nationality correctly, you can check the below implementation
const initialForm = {
nationality: '',
}
function addName(state= initialForm, action) {
switch(action.type){
case ADD_NEW_NATIONALITY:
return {
...state,
nationality: action.payload //update your `nationality` property with a plain value from `payload`
}
default:
return state
}
}

How to update only one value in nested array of Redux state?

Consider the following state:
const initState = {
id: {
data: null,
isFetching: false,
fetchingError: null
},
bookmarks: {
IDs: {
news: [],
opps: [],
posts: []
},
data: {
news: [],
opps: [],
posts: []
},
isFetching: false,
fetchingError: null
},
role: null,
membership: null,
}
How do I update just the posts array in the ÌDs array in the bookmarks array? I tried this:
case 'SET_USER_BOOKMARKED_POSTS':
return {
...state,
bookmarks: {
IDs: {
posts: action.payload
}
}
}
But when I log the state to the console the IDs array then only contains posts, while the opps and news arrays are not there anymore.
You need to destruct all inner structure:
case 'SET_USER_BOOKMARKED_POSTS':
return {
...state,
bookmarks: {
...state.bookmarks,
IDs: {
...state.bookmarks.IDs,
posts: action.payload
}
}
}
But this is not very convinient, better use lodash merge
You need use the spread operator for state.bookmarks.IDs also as when specifies keys after the spread operator, the value for the keys are overwritten.
case 'SET_USER_BOOKMARKED_POSTS':
return {
...state,
bookmarks: {
...state.bookmarks
IDs: {
...state.bookmarks.IDs,
posts: action.payload
},
}
}
The way we do it is a non-mutative method using Object.assign to basically point and update a field in the redux state. Something like:
return Object.assign({}, state.bookmarks, {
IDs: action.payload,
});
This will make sure you're not mutating the state, and returns the new bookmarks. You can adjust this to return the entire state if you'd like, it depends on if you need the other data on the update.

React redux - updating nested array in state

I'm trying some app in react redux and i have a problem with updating (push, remove, update) the nested array in state.
I have some object called service like this:
{
name: 'xzy',
properties: [
{ id: 1, sName: 'xxx'},
{ id: 2, sName: 'zzz'},
]
}
Whatever I did (in case of adding property to collection) in the reducer with the properties collection generate problem that all properties got same values as the last I had recently added -> Added property object is in service properties collection but the action replace all values in all properties in this collection.
My reducer:
export function service(state = {}, action) {
switch (action.type) {
case 'ADD_NEW_PROPERTY':
console.log(action.property) // correct new property
const service = {
...state, properties: [
...state.properties, action.property
]
}
console.log(service); // new property is pushed in collection but all properties get same values
return service
default:
return state;
}
}
I have tried some solution with immutability-helper library and it generate the same problem:
export function service(state = {}, action) {
case 'ADD_NEW_PROPERTY':
return update(state, {properties: {$push: [action.property]}})
default:
return state;
}
For example when I add new property { id: 1, sName: 'NEW'} to example above I will get this state:
{
name: 'xzy',
properties: [
{ id: 1, sName: 'NEW'},
{ id: 1, sName: 'NEW'},
{ id: 1, sName: 'NEW'}
]
}
Can someone help? :)
Make a copy of action.property as well. Whatever is dispatching this action, it could be reusing the same object.
export function service(state = {}, action) {
switch (action.type) {
case 'ADD_NEW_PROPERTY':
console.log(action.property) // correct new property
const service = {
...state,
properties: [
...state.properties,
{ ...action.property }
]
}
console.log(service); // new property is pushed in collection but all properties get same values
return service
default:
return state;
}
}
I'd recommend you to use Immutable data https://facebook.github.io/immutable-js/docs/#/List
import { fromJS, List } from 'immutable';
const initialState = fromJS({
propeties: List([{ id: 1, sName: 'xyz' }]
}
function reducer(state = initialState, action) {
case ADD_NEW_PROPERTY:
return state
.update('properties', list => list.push(action.property));
// ...
}
Your service reducer should probably look somewhat like this:
// Copy the state, because we're not allowed to overwrite the original argument
const service = { ...state };
service.properties.append(action.property)
return service
You should always copy the state before returning it.
export default function(state = {}, action) {
switch(action.type) {
case 'GET_DATA_RECEIVE_COMPLETE': {
const data = action.firebaseData;
const newState = Object.assign({}, state, {
data
});
return newState
}
default:
return state;
}
}

react redux merge state with given array

Lets say I have a reducer which is like :
const initialState = [
{
accessToken: null,
isLoggedIn: false,
}
]
export default function my_reducer(state = initialState, action) {
switch (action.type) {
case LOGIN:
return state.merge(user: action) ---> how to handle this
and the output should be like:
[
{
accessToken: null,
isLoggedIn: false,
user: {
name: 'some name',
email: 'some email'
}
]
In action I am getting a array which I am providing by doing JSON.stringify(response)
previous data should not be changed and new data should be updated
The ES6 Way
To create a new object with the state in ES6 we can use the spread operator. Like this
...
case ActionType.SUCCESS_GET_DATA : {
let newState = { ...state, [action.uniqueKey]: action.payload };
return state.merge(newState);
}
...
I did the uniqueKey part as a variable because you will want a unique name for your state.
IMO this syntax is much easier to understand than the Object.assign
You can use Object.assign() function:
var state = {
accessToken: null,
isLoggedIn: false,
};
var user = {
name: 'some name',
email: 'some email'
};
var newState = Object.assign({}, state, {user});
console.log(newState);
First I see that your state is actually an array, but I think you would need an object right?
So it would be:
const initialState = {
accessToken: null,
isLoggedIn: false,
}
(requires Babel) So with spread operator you can:
return {
...initialState,
user: {
name: '...',
surname: '...'
}
};
Or if you do not transpile via Babel alike:
return Object.assign({}, initialState, {
user: {
name: '...',
surname: '...'
}
});
Using ES6 spread syntax
...
case 'ACTION_TYPE_A': {
return { ...state, action.key: action.value };
}
...
This will return the merged state by updating the 'key' if it exists in the original state.
Everything according to new Es6 format : )
A total addToDo reducer function where the data is appended to the previous state. And you get the output of a new state data : )
export const addToDo = (state, action) => {
const { name, email, phone, image,key } = action;
var data = [...state.data];
var newData = {
name: name, email: email, phone: phone, image: image,key:key
}
data.push(newData)
return(
state.merge({
data : data
})
)};
Happy coding.

Categories