I have a ReactJS component that renders a list of orders.
I get orders from a REST API. The format of the data is the following:
{
"count": 2,
"orders": [
{
"order_number": 55981,
"customer_number": 24742
},
{
"order_number": 55980,
"customer_number": 24055
}
]
}
Each order can have a list of items. When I click on an order, I get the list of items in the following format:
{
"count": 2,
"items": [
{
"name": "Green pillow",
"status": "pending"
},
{
"name": "Red pillow",
"status": "delivered"
}
]
}
The orders list is refreshed automatically and can change any time, so I store the orders list in this.state which gets updated via ajax.
this.state looks like this:
{
"orders": [
{
"order_number": 55981,
"customer_number": 24742
},
{
"order_number": 55980,
"customer_number": 24055
}
]
}
My problem is that I would like that, when I click on an order, the state gets updated so that the clicked order contains the items associated to that order. The order list would look like this after clicking on an item:
{
"count": 2,
"orders": [
{
"order_number": 55981,
"customer_number": 24742,
"items": [
{
"name": "Green pillow",
"status": "pending"
}
]
},
{
"order_number": 55980,
"customer_number": 24055
}
]
}
How can I add items to a specific order using this.setState()? The problem is that setState seem to update data using keys, but my orders are in an array. I can probably copy the whole array and put the items key inside, but that seems overkill.
Am I taking the wrong approach?
I'm not entirely sure I got your question, but I think that what you're trying to achieve is add new orders to an array (push) which is located in your state.
If that's the case, you should something like this:
// since you're orders it's not a plain array, you will have
// to deep clone it (e.g. with lodash)
let orders = _.clone(this.state.orders);
orders.push(newOrder);
this.setState(orders);
Why cloning the state before changing it is important?
Immutability comes with some great properties (like easy equality comparison) and the React team is aiming towards that direction to improve performance more and more.
As of React 0.13 mutating state without calling this.setState will trigger a warning, and it will break entirely at some point in React's future (see the docs)
Hope this helps
Make a copy of the current state, modify the copy and use it as the new state.
This is just a simple example. You might want to add some caching logic so you don't have to retrieve the order's item again and again when a user click on the same order multiple times.
var updatedOrder = _.clone(this.state.orders);
updatedOrder[0]["items"] = [ { "name": "Foo", "status":"bar" } ];
this.setState({
orders: updatedOrder
});
Related
I am currently building a web tool which enables the user to generate a package of options in the form of a String. To select which options he wants he uses a form with different inputs (radio, checkbox) which is generated from a dictionary.json that currently holds all available options and their codes in the following format (subject to change):
[
{
"id": 0001,
"title":"foo",
"type":"radio",
"options":[
{
"bar":"",
"foo":"489",
"foobar":"489+490"
}
]
},
{
"id": 0002,
"title":"something",
"type":"check",
"options":[
{
"everything":"M016",
"evenmore":"M139"
}
]
},
[...]
As you can see it is basically a small database. The problem is that the options depend on each other so if foo is foobar it might determine that something is definitely evenmore and can NOT be changed to everything. How would I map these dependencies in the dictionary.json so that the generated form can reliably grey out options that are determined by other choices?
The structure has to be flexible so new dependencies can be inserted and would generate the new form reliably or validate existing outputs against them. There could also be options that depend on multiple other options. I can't think of a smart way of saving these dependencies and I wonder if JSON is the right format to go with here.
Any tips or ideas are welcome. Thanks!
You could try to save every option as one object which stores all the options which will be excluded if that option is selected.
So your JSON could look like the following:
[
{
"id": 0001,
"title":"foo",
"type":"radio",
"options":[
{
"bar":"",
"excludes": []
},
{
"foo":"489",
"excludes": []
},
{
"foobar":"489+490",
"excludes": [
{
"id": 0002,
"options": [
"everything"
],
},
{
"id": 0003,
"options": [
"apple",
"cherry"
],
},
]
}
]
},
{
"id": 0002,
"title":"something",
"type":"check",
"options":[
{
"everything":"M016",
"excludes": []
},
{
"evenmore":"M139",
"excludes": []
}
]
},
[...]
Everytime an option is selected you would have to check their excludes list and diable all those options for the specific fields.
To improve the usability you could check there is only one option left for a field, select this option and then disable the whole field.
EDIT:
Additionally you could save a isExcludedBy field to each of the options.
The everything option of id 0002 would then look like this:
"isExcludedBy": [
"id": 0001,
"options": [
"foobar"
]
]
This would be kind of redundant, but depending on what you want your UI to show, it could save you some computing time.
A possible simple solution (which answers your question):
// dictionary.json
{
"options": [
{
"id": 0001,
"title":"foo",
"type":"radio",
"options":[
{
"bar":"",
"foo":"489",
"foobar":"489+490"
}
]
}
// etc.; same as before
],
// this is it:
"dependencies": [
[["0001", "foobar"], ["0002", "evenmore"]],
]
}
dependencies here consist of pairs of [path to option in options that implies another option, path to the implied option].
You could make a Map data structure out of this directly (the implying options are keys, the implied are values).
This assumes that one option can imply only one other option (but it still allows for options that depend on multiple other options).
You could of course easily extend that like so:
[["0001", "foobar"], [["0002", "evenmore"], ["0003", "namaste"]]]
This would mean that "0001"/"foobar" implies both "0002"/"evenmore" and "0003"/"namaste". But perhaps YAGNI. :)
One way to approach this is to model the domain you're actually expressing, and generate the form based on that. For example, we know that apartments have street numbers, and apartment numbers, whereas houseboats don't even have streets.
{
"dwelling": {
"type": "houseboat",
"latitude": null,
"longitude": null,
}
}
or
{
"dwelling": {
"type": "apartment",
"street": "Beech St.",
"street_number": 123,
"apartment_number": 207,
}
}
By modelling the domain rather than the form, you can write rules that apply beyond the form, and you won't have to develop a mini-language for expressing form dependencies.
I need to store the data in Redis in the following structure.
{
"userId":1,
"latitude":44.24,
"longitude":-100.24,
"items": [
{
"name":"Rollerball Pen",
"attributes":[
{"Weight":"10 grams"},
{"Manufacturer":"Luxor"}
]
},
{
"name":"Measuring Tape"
}
]
},
{
"userId":2,
"items": [
{
"name":"Laptop",
"attributes":[
{"Brand":"DELL"}
]
},
{
"name":"Scissor"
}
]
},
{
"userId":3,
"latitude":47.24,
"longitude":-102.37
},
{
"userId":4
}
The key is the user id, probably like this:
"user:" + userId.toString()
Note that following are optional:
a. Latitude and Longitude
b. array of items
c. array of attributes (in items)
I have tried to sum up every possible use case using the 4 different user ids.
How can I store and access this kind of data in Redis, kindly help me out. The language that I am using is JavaScript.
Possible duplicate:
Add an Item in OrderedMap with Immutable.js
Working with redux store and Immutable js OrderedMap.
Redux store structure:
{
"app": {
"name": "Test app",
"dataPack":{
"chemID": "aaaa",
"item": {
"yp57m359": {
"solid": 33,
"liquid": 45,
"gas": 65
},
"h58wme1y": {
"solid": 87,
"liquid": 9,
"gas": 30
},
"dff56fhh": {
"solid": 76,
"liquid": 43,
"gas": 77
}
}
}
}
}
Reducer code:
return state.setIn(["app","dataPack","item",action.item_id],
fromJS({
"solid": action.soildVal,
"liquid": action.liquidVal,
"gas": action.gasVal
}));
where action.item_id is the random id (key for every item).
Above code works perfectly for adding items.
Problem is: Items stored in a random position. I need to keep the order I am adding. Need to add every item as last entry inside item. Adding one by one item is not in same order.
Help me to get a clear solution for this.
An OrderedMap will remember the order you put things in it. Every time you call .set(key, value) with a new key on an OrderedMap, it will get added to the end.
let state = Immutable.Map({
"app": Immutable.Map({
"name": "Test App",
"dataPack": Immutable.Map({
"chemId": "aaaa",
"items": Immutable.OrderedMap({}) // this is the thing we want ordered
})
})
});
state = state.setIn(['app', 'dataPack', 'items', 'yp57m359'], Immutable.Map({
'first': 'item'
}));
state = state.setIn(['app', 'dataPack', 'items', 'h58wme1y'], Immutable.Map({
'second': 'item'
}));
state = state.setIn(['app', 'dataPack', 'items', 'dff56fhh'], Immutable.Map({
'third': 'item'
}));
console.log(state.toJS()); // notice things are in order
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>
It's hard to tell exactly where your problem is because we can't see how you created your store, but my guess would be that "item" is pointing to a regular old Map instead of an OrderedMap. If you're just using fromJS(data) to create your state, it will default to using a Map instead of an OrderedMap.
I have array of users who have a property array 'rights' and I want to filter out the users who have specific rights. I would like to filter by an array so if I wanted all the users with full rights ['full'] or users with both full and edit ['full','edit']. I am fairly new to using lodash and I think I can chain some together but I am not sure if this is there are more efficient ways of doing it.
Here is my plunker: http://plnkr.co/edit/5PCvaDJaXF4uxRowVBlK?p=preview
Result ['full'] :
[{
"name": "Company1 Admin",
"rights": [
"full"
]
},
{
"name": "FullRights Company1",
"rights": [
"full","review"
]
}]
Result ['full','edit']:
[{
"name": "Company1 Admin",
"rights": [
"full"
]
},
{
"name": "FullRights Company1",
"rights": [
"full","review"
]
},
{
"name": "EditRights Company1",
"rights": [
"edit"
]
}]
Code:
var users = [
{
"name": "Company1 Admin",
"rights": [
"full"
]
},
{
"name": "FullRights Company1",
"rights": [
"full","review"
]
},
{
"name": "ApproveRights Company1",
"rights": [
"approve","review"
]
},
{
"name": "EditRights Company1",
"rights": [
"edit"
]
},
{
"name": "ReviewRights Company1",
"rights": [
"review"
]
},
{
"name": "NoRights Company1",
"rights": [
"none"
]
}
];
var tUsers = [];
var filterRights = ['full','edit'];
_.forEach(users, function(user) {
if (_.intersection(user.rights, filterRights).length > 0) {
tUsers.push(user);
}
}) ;
//console.log('users', JSON.stringify(users, null, 2));
console.log('tUsers', JSON.stringify(tUsers, null, 2));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
From the docs
_.filter(collection, predicate, thisArg);
Arguments
collection (Array|Object|string): The collection to iterate over.
[predicate=_.identity] (Function|Object|string): The function invoked per iteration.
[thisArg] (*): The this binding of predicate.
Chaining is great when you want to connect different processing steps.
If your problem statement was to
filter by rights
sort by oldest person
take 10
Then chaining would make a lot of sense.
This problem seems to be mostly custom logic on filtering.
var users = [/* Your user data here */];
function filterByRights (users, rights) {
return _.filter(users, function (user) {
return _.any(user.rights, function (right) {
return _.contains(rights, right);
});
});
}
filterByRights(users, ['full', 'edit']); // [/*Users with full or edit rights*/]
I think my example is good becuase it doesn't depend on conditional logic. It uses lodash defined methods like any and contains
Performance concerns
I want to expand on what performance concerns you have. Here are a couple of points.
Your question code is maintaining its own mechanism for filtering out users. While it is a perfectly good solution you should opt into letting the guys who maintain lodash handle this logic. They have probably spent a lot of time optimizing how to create another array from an original one.
_.any is more efficient than _.intersection. _.intersection needs to process every element to know what the intersection is. _.any stops when it hits the first element which passes the predicate otherwise it checks each of them. This point is minor since there are a small number of "rights"
The example I've given is probably more "lodash standard". You typically can do data transformations completely with lodash defined methods and trivial predicates.
Here is an update to #t3dodson 's answer. You should now use the following snippet if using current (4.17.4) Lodash version:
function filterByRights (users, rights) {
return _.filter(users, function (user) {
return _.some(user.rights, function (right) {
return _.includes(rights, right);
});
});
}
From the Changelog:
Removed _.contains in favor of _.includes
Removed _.any in favor of _.some
I think you were on the right path with intersection() (I've never seen any performance issues with this function). Here's how I would compose an iteratee using flow():
_.filter(users, _.flow(
_.property('rights'),
_.partial(_.intersection, filterRights),
_.size
));
The property() function gets the rights property, and passes it to intersection(). We've already partially-applied the filterRights array. Lastly, the size() function is necessary to pass a thruthy/falesy value to filter().
Consider this example collection:
{
"_id:"0,
"firstname":"Tom",
"children" : {
"childA":{
"toys":{
'toy 1':'batman',
'toy 2':'car',
'toy 3':'train',
}
"movies": {
'movie 1': "Ironman"
'movie 2': "Deathwish"
}
},
"childB":{
"toys":{
'toy 1':'doll',
'toy 2':'bike',
'toy 3':'xbox',
}
"movies": {
'movie 1': "Frozen"
'movie 2': "Barbie"
}
}
}
}
Now I would like to retrieve ONLY the movies from a particular document.
I have tried something like this:
movies = users.find_one({'_id': 0}, {'_id': 0, 'children.ChildA.movies': 1})
However, I get the whole field structure from 'children' down to 'movies' and it's content. How do I just do a query and retrieve only the content of 'movies'?
To be specific I want to end up with this:
{
'movie 1': "Frozen"
'movie 2': "Barbie"
}
The problem here is your current data structure is not really great for querying. This is mostly because you are using "keys" to actually represent "data points", and while it might initially seem to be a logical idea it is actually a very bad practice.
So rather than do something like assign "childA" and "childB" as keys of an object or "sub-document", you are better off assigning these are "values" to a generic key name in a structure like this:
{
"_id:"0,
"firstname":"Tom",
"children" : [
{
"name": "childA",
"toys": [
"batman",
"car",
"train"
],
"movies": [
"Ironman"
"Deathwish"
]
},
{
"name": "childB",
"toys": [
"doll",
"bike",
"xbox",
],
"movies": [
"Frozen",
"Barbie"
]
}
]
}
Not the best as there are nested arrays, which can be a potential problem but there are workarounds to this as well ( but later ), but the main point here is this is a lot better than defining the data in "keys". And the main problem with "keys" that are not consistently named is that MongoDB does not generally allow any way to "wildcard" these names, so you are stuck with naming and "absolute path" in order to access elements as in:
children -> childA -> toys
children -> childB -> toys
And that in a nutshell is bad, and compared to this:
"children.toys"
From the sample prepared above, then I would say that is a whole lot better approach to organizing your data.
Even so, just getting back something such as a "unique list of movies" is out of scope for standard .find() type queries in MongoDB. This actually requires something more of "document manipulation" and is well supported in the aggregation framework for MongoDB. This has extensive capabilities for manipulation that is not present in the query methods, and as a per document response with the above structure then you can do this:
db.collection.aggregate([
# De-normalize the array content first
{ "$unwind": "$children" },
# De-normalize the content from the inner array as well
{ "$unwind": "$children.movies" },
# Group back, well optionally, but just the "movies" per document
{ "$group": {
"_id": "$_id",
"movies": { "$addToSet": "$children.movies" }
}}
])
So now the "list" response in the document only contains the "unique" movies, which corresponds more to what you are asking. Alternately you could just $push instead and make a "non-unique" list. But stupidly that is actually the same as this:
db.collection.find({},{ "_id": False, "children.movies": True })
As a "collection wide" concept, then you could simplify this a lot by simply using the .distinct() method. Which basically forms a list of "distinct" keys based on the input you provide. This playes with arrays really well:
db.collection.distinct("children.toys")
And that is essentially a collection wide analysis of all the "distinct" occurrences for each"toys" value in the collection, and returned as a simple "array".
But as for you existing structure, it deserves a solution to explain, but you really must understand that the explanation is horrible. The problem here is that the "native" and optimized methods available to general queries and aggregation methods are not available at all and the only option available is JavaScript based processing. Which even though a little better through "v8" engine integration, is still really a complete slouch when compared side by side with native code methods.
So from the "original" form that you have, ( JavaScript form, functions have to be so easy to translate") :
db.collection.mapReduce(
// Mapper
function() {
var id this._id;
children = this.children;
Object.keys(children).forEach(function(child) {
Object.keys(child).forEach(function(childKey) {
Object.keys(childKey).forEach(function(toy) {
emit(
id, { "toys": [children[childkey]["toys"][toy]] }
);
});
});
});
},
// Reducer
function(key,values) {
var output = { "toys": [] };
values.forEach(function(value) {
value.toys.forEach(function(toy) {
if ( ouput.toys.indexOf( toy ) == -1 )
output.toys.push( toy );
});
});
},
{
"out": { "inline": 1 }
}
)
So JavaScript evaluation is the "horrible" approach as this is much slower in execution, and you see the "traversing" code that needs to be implemented. Bad news for performance, so don't do it. Change the structure instead.
As a final part, you could model this differently to avoid the "nested array" concept. And understand that the only real problem with a "nested array" is that "updating" a nested element is really impossible without reading in the whole document and modifying it.
So $push and $pull methods work fine. But using a "positional" $ operator just does not work as the "outer" array index is always the "first" matched element. So if this really was a problem for you then you could do something like this, for example:
{
"_id:"0,
"firstname":"Tom",
"childtoys" : [
{
"name": "childA",
"toy": "batman"
}.
{
"name": "childA",
"toy": "car"
},
{
"name": "childA",
"toy": "train"
},
{
"name": "childB",
"toy": "doll"
},
{
"name": "childB",
"toy": "bike"
},
{
"name": "childB",
"toy": "xbox"
}
],
"childMovies": [
{
"name": "childA"
"movie": "Ironman"
},
{
"name": "childA",
"movie": "Deathwish"
},
{
"name": "childB",
"movie": "Frozen"
},
{
"name": "childB",
"movie": "Barbie"
}
]
}
That would be one way to avoid the problem with nested updates if you did indeed need to "update" items on a regular basis rather than just $push and $pull items to the "toys" and "movies" arrays.
But the overall message here is to design your data around the access patterns you actually use. MongoDB does generally not like things with a "strict path" in the terms of being able to query or otherwise flexibly issue updates.
Projections in MongoDB make use of '1' and '0' , not 'True'/'False'.
Moreover ensure that the fields are specified in the right cases(uppercase/lowercase)
The query should be as below:
db.users.findOne({'_id': 0}, {'_id': 0, 'children.childA.movies': 1})
Which will result in :
{
"children" : {
"childA" : {
"movies" : {
"movie 1" : "Ironman",
"movie 2" : "Deathwish"
}
}
}
}