How can I update deeply nested object inside array? - redux

Hello good people of the stack!
I am working on a react-redux application and I am trying to update a property on a deeply nested structure in my reducer. The data structure is as follows and I want to update the text property:
state = {
assessment: {
requirements: [
questions: [
{
text
}
]
]
}
}
so I have tried the following:
// reducer code...
return {
...state,
[assessmentId]: {
...state[assessmentId],
requirements: [
...state[assessmentId].requirements,
[requirementId]: [
...state[assessmentId].requirements[requirementsId],
questions: [
...state[assessmentId].requirements[requirementsId].questions,
[questionId]: {
text: action.payload.response.text
},
],
] ,
],
},
};
This is more pseudo code than actual code to remove complexity.
I do not see any change in redux dev tools so I am wondering if I have made a mistake the way I get the nested objects and array elements.
I was also curious about using combine reducers here. I asked a colleague and they suggested to use that but I am unsure how you would take that approach here. As always, any help is appreciated.

I recommend immer for deep state changes in your reducers.
It adds a little weight to your bundle, and you'll get better performance from using the spread operator, but if you can live with that it'll make your code easier to read and write.
import produce from "immer";
// reducer code...
return produce(state, draft => {
draft[assessmentId].requirements[requirementsId].questions[questionsIndex].text = action.payload.response.text;
});
I'd say your issue stems from questions being an array which will take a little more work to keep straight than object based state.
As it is you appear to be trying to set the question value as if questions was an object. Maybe you just need to drop the [questionId] syntax, eg
questions: [
...state[assessmentId].requirements[requirementsId].questions,
{ text: action.payload.response.text },
],
This will set the text object as a new item on the end of the array though.
Depending on what you need to do (ie what already exists in the array and whether you are trying to add or update) you'll want to have a read of:
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#inserting-and-removing-items-in-arrays
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#updating-an-item-in-an-array

Related

How to use normalized state in Redux?

I need help designing a simple app which allows user to rate videos using a form. My state is composed by 2 reducers, one that holds data about all ratable videos (in a normalized fashion) and another one that holds the form state:
{
videos: {
'video1Id': { id: 'video1Id', title: 'Cat video', duration: 120, ... },
'video2Id': { ... },
...
},
rateForm: {
'videoId': 'video1Id'
'userComment: 'A nice video about cat'
'formSubmitted': false
...
}
}
Note that, inside rateForm, I reference the video id instead of the video object. Problem is, how can I retreive the whole video object from my rateForm reducer ?
I feel like I'm following the best practice of Redux design but I'm stuck at this really simple use case. Any help appreciated.
Thanks
One thing to remember, reducer should be AS SIMPLE AS POSSIBLE. Only doing atomic operations on reducer level. From what I can tell you trying to retrieve the whole video object in your reducer just doesn't sound right.
Depending on your needs, usually, you don't need to fetch the whole video object if you just want to comment on it or rate it. But if you are 100% sure you have to, A good place to do this is in your action. Using Redux-Thunk, you will have access to the whole state object before you return your thunk. Example
function doSomethingToVideo (videoId, something) {
return (dispatch, getState) => {
const video = getState().videos[videoId]
// Do what ever
return somethingElse
}
}
Reference: Redux author's answer on a similar matter.
Accessing Redux state in an action creator?

Normalizr and recursive nested structure

I'm using normalizr to flatten a structure like the following one:
{
"fields":[{
"id":"29",
"text": "something"
}, {
"id":"16",
"text": "something"
"fields":[{
"id":"17",
"text": "something"
}]
}, {
"id":"18",
"text": "something"
}
}
My structure has an array of fields and a field may also have nested fields. There's only one level of nesting allowed.
What I'm trying to do is this:
const block = new schema.Entity('fields')
const blockList = new schema.Array(block)
block.define({
fields: blockList
})
const normalizedData = normalize(originalData, blockList)
After running this snippet, normalizedData has a results property and it only has the first level of field ids, even though entities has all the fields normalized, including the nested ones.
I'd like to have in the results array all the ids, including the nested ones. What am I missing?
I'd like to have in the results array all the ids, including the nested ones. What am I missing?
This is how Normalizr works. The returned result is always in the same format of the input data. You won't be able to get what you are asking for from Normalizr.
If, however, you're just looking for a list of blocks, pull that from the entities:
const blockIds = Object.keys(normalizedData.entities.blocks);
You should consider using a normalized form for your data structure in redux.
This is advisable if your application need growth in complexity.
There is an interesting article on redux docs.
http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
A normalized form take some ideas from db counterpart and Normalizr works in that way, so your request does not really match with how Normalizr works.
Please consider #Paul Armstrong answer for a work around if you really need get blocks in that way.

How to update a value of a nested object in a reducer?

I have built my state like so
const list = {
categories: {
Professional: {
active: false,
names: [
{
id: 1,
name: "Golf",
active: false
},
{
id: 2,
name: "Ultimate Frisbee",
active: false
}
]
}}
In my action I have added an ID option so I would like to change the active status when the user clicks the option to do so
I am using Immutable JS though not married to it. I am wondering how I could target the id of the object and update its active status in a reducer? I am also open to feedback on how to better improve my state
This is very common thing, and, actually, quite daunting. As far as I know, there is no really good and well-adopted solution in plain JS. Initially, Object.assign approach was used:
return Object.assign({}, state, {
categories: Object.assign({}, state.categories, {
Professional: Object.assign({}, state.Professional, {
active: true
})
})
});
This is too straightforward and cumbersome, I admit it, but I have to say that we've built few big applications with this approaches, and except for number of characters it is not bad. Nowadays, the most popular approach is using Object spread:
return {
...state,
categories: {
...state.categories,
Professional: {
...state.categories.Professional,
active: true
}
}
}
The second approach is much more cleaner, so if you use plain JS, then it seems as a good choice.
In Immutable.js I have to admit it is easier, you just do the next
return state.updateIn(['categories', 'Professional'], x => x.set('active', true));
But it has it's own drawbacks and caveats, so it is better to think about it seriously before committing to it.
And your last question regarding improving the state – usually it is better not to have such deep nesting (separate your concerns, very often fields don't depend on each other – like active status can be separated to another object), but it is hard to say because of no knowledge of your domain. Also, it is considered as normal thing to normalize your data.
The Redux docs section on Structuring Reducers covers this. In particular, see the section on Immutable Update Patterns. The examples given are for plain JS objects and arrays, but the same approach applies - map() over the list, return the existing item for everything you don't want to update and return a new version for the one you do want to update.
Per the docs, also note that it's easier to update a specific item if your data is stored in a normalized structure, since you can look it up by ID directly. The "Structuring Reducers" section covers normalization as well.

Meteor - How do you exclude properties in a child collection from being published?

Imagine you have a collection similar to the following...
Tests = [
{
name: 'Some Test',
questions: [
{ question: 'Answer to life, the universe, and everything?', answer: '42' },
{ question: 'What is your favorite color?', answer: 'Blue' },
{ question: 'Airspeed velocity of unladen European Swallow?', answer: '24 mph' }
]
}
];
How do you publish the entire collection except for the answer property?
I understand you can do the following to omit properties from the publish...
Meteor.publish('tests', function() {
return Tests.find({}, {fields: {name:0}});
});
But I'm not sure how to omit a property from an array property.
Thanks!
It can't be done the way you want to do it. Meteor only supports field specifiers that are 1 level deep. You can sometimes get a sub-field specifier to work, but it's not reliable.
You can put your questions into their own collection with a testId field that links them back to the test, relational style. One question per document, and then you'll be able to specify that only the question field gets published.
Meteor.publish ('questions', function(testId) {
return Questions.find({testId: testId}, {fields: {question: 1}})
});
It's not ideal, but pretty painless compared to trying to find a workaround that allows your questions to live in the test document.
There might be a way to do this manually with a more involved publish. There's a similar question here with an answer that gets into it.

Using the deep:true query option with a dojo ObjectStore adapter

I'm new to Dojo, and I'm trying to get my head wrapped around some pretty basic concepts before my partner and I embark on a new project. I've been successful in using the ItemFileWriteStore to get data returned from an ajax request into a DataGrid, but I would like to use the new Memory store instead. I am using the ObjectStore adapter to wrap the Memory store, and data is being populated in the grid.
One thing has me worried. In the example code I'm running, there is some nested nodes in the JSON object I'm using to populate the grid. It's one of the Countries JSON object they use in the dojo documentation. Here's a little snippet of that object:
'items': [
{ 'name':'Africa', 'type':'continent', children:[
{ 'name':'Egypt', 'type':'country' },
{ 'name':'Kenya', 'type':'country', children:[
{ 'name':'Nairobi', 'type':'city' },
{ 'name':'Mombasa', 'type':'city' } ]
},
{ 'name':'Sudan', 'type':'country', 'children':
{ 'name':'Khartoum', 'type':'city' }
} ]
},
]
In the DataGrid instantiation, I send in a query and then I set the qureyOptions to {deep:true} like so:
var grid = new DataGrid({
style: "width: 500px; height: 300px;",
store: this._geoStore,
structure: layoutGeo,
rowSelector: '20px',
columnReordering: true,
query: {},
queryOptions: { deep: true },
rowsPerPage: 20
}, document.createElement("div"));
When I use the old ItemFileWriteStore, this works fine, and ALL the nested data is essentially flattened out and placed in the grid as I would expect. However, when I switch it over to a Memory store wrapped in an ObjectStore adapter, the only data displayed in the grid is the top most parent of the data object. In the case of the sample data, only the data associated with Africa, but none of its children, are displayed. So it seems that the queryOptions {deep:true} statement has no affect when using the Memory store.
Is there some way of getting nested data within the JSON objects into a data grid using a Memory store wrapped in an ObjectStore? I would have thought that the wrapper class would have taken care of this, and it might, but I don't know how to fix it.
For all who help, thank you very much for your time and willingness to share your expertise. I really have studied the dojo docs and web for a long time trying to figure this stuff out. I fear I may have glossed over something obvious and am hoping you all can help.
I blogged about the store changes at http://swingingcode.blogspot.com/2012/03/dojo-implementing-viewmodelstore.html
The code here uses the new store object but brings the reference notation from the data api. All objects would be considered top level objects and query-able. I did not implement the logic to pull out nested objects.

Resources