I am working on a react redux app. I have state object like
const initialState = {
articles: []
};
each item in 'articles' is like
{
id:1,
data:'data'
}
I need to add another object to the array without removing the existing objects in the array.
I tried
return {...state,articles: [...state.articles] };
in my reducer but it ends up removing the existing items.
what should it be so that it does not remove existing items?
In Reducer return using immutable update pattern
return { ...state, articles: action.payload.data };
for more information on reducer immutable pattern.
You should spread your current articles into a new array as you did, and append the payload from your action.
return { ...state, articles: [...state.articles, payload] }
An example of how your action should look:
export function addArticle(article) {
return { type: "ADD_ARTICLE", payload: { article } };
}
Related
I have just started to learn redux and I was wondering how to access object component in redux reducers. I want to access the address component inside the user but is unable to because it is the key. Is there a way to access it? Here is my code:
const initialState = {
token: localStorage.getItem('token'),
user: {},
};
export default function (state = initialState, action) {
const { type, payload } = action;
case SET_SHIPPING_ADDRESS:
return {
...state,
state.user.addresses: payload, //how to access the addresses property from here?
}
};
The spread operation "..." lets you assign a new value to the properties of an object.
if one of the properties is an object and you want to assign a new value to one of its properties, you need to spread it as well and so on.
For example:
export default function (state = initialState, action) {
const { type, payload } = action;
case SET_SHIPPING_ADDRESS:
return {
...state,
user: {
...state.user,
addresses: payload
}
}
};
You have a user property in your initial state but then you try to assign another property state.user.buyer.addresses which is wrong because what you actually want to do is update the addresses inside the user object.
Easy and quick way to deep clone your user object would be to stringify it and then parse it.
const clone = JSON.parse(JSON.stringify(state.user));
then you could assign the new addresses
clone.buyer.addresses = payload.addresses
Finally update your state with the new user object
case SET_SHIPPING_ADDRESS:
return {
...state,
user: clone
}
I am using Redux to manage the state of my react app. I am creating an object, then passing this object to addTile function which is my action.
So my action.ts looks like this:
export function addTile(tile){
return {
type: "ADD_TILE",
payload: tile
}
}
My reducer.ts looks like this:
const reducer = (state = {
isPanelOpen: false,
isDiscardAllChangesOpen: false,
tiles: [],
tempTiles: [],
}, action) => {
switch (action.type) {
case "PANEL_VISIBILITY":
state = {
...state,
isPanelOpen: action.payload
};
break;
case "ADD_TILE":
state = {
...state,
tiles: [...state.tiles, action.payload]
}
break;
}
return state;
};
export default reducer;
However, if I try to use this in my component like this:
this.props.addTile(tile)
alert(this.props.tiles.length)
The length will be 0. However, the item is really added to the array, but at the time of the alert execution, the length was 0. From my reading on Redux docs, actions by default are async (or at least that's how I understand they are).
I even try to do this:
this.props.addTile(tile)
.then(response => { //some code})
Then I get cannot read property then of undefined.
Any ideas?
When you try to check the prop right after dispatching an action, React has not yet had a chance to re-render. It's not that it's "taking too long", it's that your own code is still executing. So, React has not re-rendered your component, and the prop value is still the same.
Your promise example would only work if addTile() was a thunk that returned a promise.
So I have an action that is fetching from an API.
I am fetching the user from my API, and it returns the JSON with and returns with the action and the json values for the user and a progress value.
fetchUser: (userId) => {
return dispatch => {
dispatch({ type: Constants.USER_FETCHING });
let url = 'http://localhost:4001/api/v1/users/'+ userId;
axios.get(url)
.then(function(data) {
console.log('data returned is ' + JSON.stringify(data));
dispatch({
type: Constants.GET_USER,
user: data.user,
progress: data.progress,
});
});
};
},
Now in my reducer I have to return the state without mutating it:
import Constants from '../constants';
const initialState = {
users: [],
user: null,
progress: null,
fetching: false,
};
const users = (state = initialState, action) => {
case Constants.USER_FETCHING:
return {...state, fetching: true};
switch (action.type) {
case Constants.GET_USER:
return ?????;
default:
return state;
}
};
export default users;
How exactly should I be returning the state?
I see examples using Object.assign but not sure how to structure it for my case:
return Object.assign({}, state, ????);
You can use the spread operator ... as you did in your USER_FETCHING:
case Constants.GET_USER:
return {
...state,
user: action.user,
progress: action.progress
};
This creates a new object by first setting the same properties as they currently are on state, and then overwrites the user and progress properties with the new values from the action.
Both Karbaman and Tony answers are correct. Furthermore, the Object spread is compiled to Object.assign by default if you're using babel, as you can read in the documentation.
To add to those answers, if you want to update the users array, you can use the spread transform (documentation):
case Constants.GET_USER:
return {
...state,
user: action.user,
progress: action.progress,
users: [ ...state.users, action.user ]
};
Which will create a new array for users, concat it with the existing one, then with the new user.
However, if the user in the action is already within the existing array, it will get duplicated. You can implement a verification to avoid that, or use directly union from lodash (if user is a String or Number):
....
users: union(state.users, [action.user])
...
If you are going to use Object.assign, then it will be:
return Object.assign({}, state, {user: action.user, progress: action.progress});
How it works:
Object.assign gets 3 objects:
{} - empty
state
{user: action.user, progress: action.progress}
and merges them into 1 object one by one.
It is important to have empty object ( {} ) as a first argument, because in this case props/values from state will be merged to empty object and you will have a new copy.
If you remove empty object or put state as a first argument - in this case everything will be merged to state and you will have a mutation instead of copying.
I am writing my first bigger React/Redux/Meteor App. I know that Redux is not necessarily needed in an Meteor App, but I want to use it.
I load a record from a MongoDB with Meteor and then I want to store this object in my Redux store. But the object gets nested in the store and I do not know why this is the case.
Here is my code so far:
Action loads the remote record
export const loadRecord = (id) => {
return dispatch => {
Meteor.call('loadRecord', id, (error, result) => {
if (!error) {
dispatch({
type: TYPE,
result
});
} else {
dispatch({
type: TYPE_ERROR,
error,
});
}
});
};
};
Reducer should update my store
const initialState = {
singleRecord: {}
};
export function singleRecord(state = initialState, action) {
switch (action.type) {
case TYPE:
return {
...state,
singleRecord: action.result
};
default:
return state;
}
}
In more store I expect something like this:
singleRecord: {
id:"1223",
text:"abcde"
}
But what I get is:
singleRecord: {
singleRecord {
id:"1223",
text:"abcde"
}
}
So my store gets updated and everything is working as expected beside the fact, that my record is nested somehow.
I think I am missing a fundamental thing, or I implemented it wrong. It would be very nice if someone can explain me if this is the intended behavior or if not, can tell me why my code is not working as intended.
Thanks in advance
You want to unwrap the payload of the action:
return {
...state,
...action.result
};
or, in other words:
return Object.assign({}, state, action.result);
I am not sure what else you want to save in singleRecord but it's entirely possible you want to do this:
return action.result;
Also, your initial state should be just const initialState = {};
The object returned from your singleRecord reducer is what is stored into singleRecord state.
context
I'm rendering a form with a dynamic set of text elements. I've normalised my state using normalizr principles so there is an array of elementIds and an object containing the element properties referenced by the elementIds (see initial state in code sample below).
aim
My aim is simply for the two rendered elements to be editable. I'm successfully dispatching an action CHANGE_ELEMENT_VALUE to my store using an onChange callback, and action.id (referencing the id of the changed element) and action.value (the new value) are available in the reducer (see code below).
problem
My problem is that the text fields aren't changing when I type, even though I can see state changing using the devtools redux extension. My understanding is that react is not recognising a state change because the change is deep in state, and I'm not successfully creating a new state object, I'm probably referencing old instances somehow.
reducer code
Below is my bungled attempt to force a new state object. I'm assuming it's not working because my components are not being re-rendered. It also seems very inelegant.
let initialState = {
isLoading: false,
data: {
elementIds: ['name', 'email'],
elements: {
'name': {'id': 'name', 'value':'ben'},
'email': {'id':'email', 'value':'ben#test.com'},
},
},
error: false
}
function formReducer(state = initialState, action = null) {
switch(action.type) {
case types.CHANGE_ELEMENT_VALUE:
let newData = Object.assign(state.data)
newData.elements[action.id].value = action.value
return {...state, data: newData}
default:
return state;
}
}
export default formReducer;
you can make use of immutability-helper npm package and update your values in your reducer
import update from 'immutability-helper';
let initialState = {
isLoading: false,
data: {
elementIds: ['name', 'email'],
elements: {
'name': {'id': 'name', 'value':'ben'},
'email': {'id':'email', 'value':'ben#test.com'},
},
},
error: false
}
function formReducer(state = initialState, action = null) {
switch(action.type) {
case types.CHANGE_ELEMENT_VALUE:
return update(state, {
data : {
elements: {
[action.id]: {
value: {
$set: 'new value'
}
}
}
}
})
default:
return state;
}
}
export default formReducer;
update() provides simple syntactic sugar around this pattern to make
writing this code easier. While the syntax takes a little getting used
to (though it's inspired by MongoDB's query language) there's no
redundancy, it's statically analyzable and it's not much more typing
than the mutative version.
Object.assign only operates one level deep; i.e. it does not recursively clone the entire object tree. So, your top-level object is cloned but it doesn't trigger a rerender since your reducer mutates a value deep in the cloned object.
I would recommend looking into the deep-extend package and updating your state as follows:
import extend from 'deep-extend';
...
return extend(state, {
elements: {
[key]: value
}
});