Normalizr - How to handle nested entities that are already normalized - reactjs

I have entities in very nested JSON that already follow the normalizr format where the idAttribute is already the key where the object is defined:
groups: [{
id: 'foo',
families: {
smiths: {
people: [{
id: 'sam',
}, {
id: 'jake',
}],
},
jones: {
people: [{
id: 'john',
}, {
id: 'sue',
}],
},
},
}];
In this example, notice that the families attribute is using the id (smiths, jones) to identify the people who are an array of objects with ids.
The schemas for this might look like:
const person = new Entity('person');
const family = new Entity('family', {
people: [person],
}, {idAttribute: ???});
const group = new Entity('family', {
family: [family],
});
QUESTION: Is there a way to specify that a schema's idAttribute is the key where it is defined? In other words, how would I define the schema for Family as it's related to groups and people?
Another question, is there a way to denormalize a flattened state so that the families families: {[id]: obj} pattern stays the same as it is in the example json above?

Is there a way to specify that a schema's idAttribute is the key where it is defined?
Yes. The idAttribute function takes 3 arguments: value, parent, and key. Please read the docs. In your case, you can use the key, along with schema.Values
const family = new schema.Entity('families', {
people: [ person ]
}, (value, parent, key) => key);
const families = new schema.Values(family);
const group = new schema.Entity('groups', {
families
});
For denormalize, you'll need a separate schema for family, since the ID can't be derived from the key.

Related

ElasticSearch multi field search in non-nested arrays

Consider this JSON content:
{
students: [
{
student_name: aaa,
classes: [
{
name: class1,
properties: {
id: 1
}
},
{
name: class2,
properties: {
id: 2
}
}
},
{
student_name: bbb,
classes: [
{
name: class2,
properties: {
id: 1
}
}
}
]
}
students is not nested. classes is not nested. I only want to match student_name aaa document by this below query:
{
"query": {
"bool": {
"must": [
{ "term": { "classes.name": "class1" }},
{ "term": { "classes.properties.id": "1" }}
]
}
}
}
But my query is matching both aaa and bbb, because it is considering my term statements as separate queries. How can I just match student aaa?
You have to make the student as a nested type. Otherwise it gets flattened and your query will match both the documents.
From the same ElasticSearch documentation:
When ingesting key-value pairs with a large, arbitrary set of keys,
you might consider modeling each key-value pair as its own nested
document with key and value fields. Instead, consider using the
flattened data type, which maps an entire object as a single field and
allows for simple searches over its contents. Nested documents and
queries are typically expensive, so using the flattened data type for
this use case is a better option.
Please refer to examples given in the same documentation and it will be clear to you. When student is changed to nested you should be able to get your expected results.

How to convert an array of dictionaries into an array of keys using react

Given a list:
let names = [{name: "bobby"}, {name: "sydney"}, {name: "Paul"}, {name: "Grace"}
I want the output to be ["bobby", "sydney", "Paul", "Grace"]
Here is what I have tried:
var items = Object.keys(names).map(function(i) {
return names[i];
})
const items = Object.keys(names).map((key)=>names[key]);
this.setState({items});
console.log(this.state.items);
names.map(({ name }) => name)
const names = [{
name: "bobby"
}, {
name: "sydney"
}, {
name: "Paul"
}, {
name: "Grace"
}];
const keys = names.map(({
name
}) => name);
console.log(keys);
A note about react keys, they should be unique within the rendered siblings, i.e. they should be unique within the dataset. Names alone may not provide sufficient uniqueness.
A second note, you might not want to generate your react keys separately from where you need them, i.e. generally they are created when you are mapping JSX.
This is not really related to React. You can do that with JavaScript, for instance using API like map().
Here is an example:
let arr = names.map(obj => obj.name);

Multidimensional Arrays, Vuex & Mutations

I'm attempting to both add and remove items in a multidimensional array stored in Vuex.
The array is a group of categories, and each category and have a sub-category (infinity, not simply a two dimensional array).
Example data set is something like this:
[
{
id: 123,
name: 'technology',
parent_id: null,
children: [
id: 456,
name: 'languages',
parent_id: 123,
children: [
{
id:789,
name: 'javascript',
parent_id: 456
}, {
id:987,
name: 'php',
parent_id: 456
}
]
}, {
id: 333,
name: 'frameworks',
parent_id 123,
children: [
{
id:777,
name: 'quasar',
parent_id: 333
}
]
}
]
}
]
....my question is, how do I best add and remove elements to this array, which is inside of a Vuex Store?
I normally manipulate simple arrays inside the Vuex Store using Vue.Set() to get reactivity. However, because I'm not sure how deep the nested array being manipulated is - I simply can't figure it out.
Here's an example of how I thought I could add a sub-category element using recursion:
export const append = (state, item) => {
if (item.parent_uid !== null) {
var categories = []
state.data.filter(function f (o) {
if (o.uid === item.parent_uid) {
console.log('found it')
o.push(item)
return o
}
if (o.children) {
return (o.children = o.children.filter(f)).length
}
})
} else {
state.data.push(item)
}
}
The first thing to understand is that vuex, or any other state management library based on flux architecture, isn't designed to handle nested object graph, let alone arbitrary/infinity nested objects that you mentioned. To make the matter worse, even with shallow state object, vuex works best when you define the shape of the state (all desired fields) upfront.
IMHO, there are two possible approaches you can take
1. Normalize your data
This is an approach recommended by vue.js team member [here][2].
If you really want to retain information about the hierarchical structure after normalization, you can use flat in conjunction with a transformation function to flatten your nested object by name to something like this:
const store = new Vuex.Store({
...
state: {
data: {
'technology': { id: 123, name: 'technology', parent_id: null },
'technology.languages': { id: 456, name: 'languages', parent_id: 123 },
'technology.languages.javascript': { id: 789, name: 'javascript', parent_id: 456 },
'technology.languages.php': { id: 987, name: 'php', parent_id: 456 },
'technology.frameworks': { id: 333, name: 'frameworks', parent_id: 123 },
'technology.frameworks.quasar': { id: 777, name: 'quasar', parent_id: 333 },
}
},
});
Then you can use Vue.set() on each item in state.data as usual.
2. Make a totally new state object on modification
This is the second approach mentioned in vuex's documentation:
When adding new properties to an Object, you should either:
Use Vue.set(obj, 'newProp', 123), or
Replace that Object with a fresh one
...
You can easily achieve this with another library: object-path-immutable. For example, suppose you want to add new category under languages, you can create a mutation like this:
const store = new Vuex.Store({
mutations: {
addCategory(state, { name, id, parent_id }) {
state.data = immutable.push(state.data, '0.children.0.children', { id, name, parent_id });
},
},
...
});
By reassigning state.data to a new object each time a modification is made, vuex reactivity system will be properly informed of changes you made to state.data. This approach is desirable if you don't want to normalize/denormalize your data.

Normalizing my JSON response using normalizr with redux

I'm trying to reshape my Redux store so I can query by and filter my data easily.
I have an API endpoint that returns back an order.
The order looks like this at a high level:
Order
+ references
+ item_details
- category_id
- product_id
- product
So an order has many references, and the references have many item_details.
The item details has a category and product.
const data = {
id: 3939393,
discount: 0,
references: [
{
id: 123,
order_id: 3939393,
name: "order 1",
item_details: [
{
id: 100,
order_id: 3939393,
product_id: 443,
sort_order: 1,
category_id: 400,
product: {
id: 443,
name: "hello world",
price: 199
}
},
{
id: 200,
order_id: 3939393,
product_id: 8080,
sort_order: 2,
category_id: 500,
product: {
id: 8080,
name: "hello json",
price: 299
}
}
]
}
]
};
export default data;
So far my schema definitions look like this:
export const productSchema = new schema.Entity("products");
export const itemDetailSchema = new schema.Entity("itemDetails", {
product: productSchema
});
export const references = new schema.Entity("references", {
item_details: new schema.Array(itemDetailSchema)
});
export const orderSchema = new schema.Entity("orders");
const result = normalize(data, orderSchema);
console.log("result is: " + JSON.stringify(result));
How can I get the products in its own section in the normalized JSON? Currently the products are still embedded inside the JSON.
Would I use normalizr to create state "index" type looks like this:
productsInReferences: {
123: [400, 8080]
}
If not, how exactly to I generate these types of JSON lookups?
I created a codesandbox with my code so far.
https://codesandbox.io/s/xpl4n9w31q
I usually think Normalization schemes from the deepest nested structure all the way to the one containing the data for those cases. Remember that you can do explicit array definition through [someSchema], also every level should be contained on a nested schema, in this case you forgot the references: [referenceSchema] on the orderSchema.
The correct normalization would be:
// Product Schema
const productSchema = new schema.Entity("products");
// Item detail schema containing a product schema OBJECT within the property product
const itemDetailSchema = new schema.Entity("itemDetails", {
product: productSchema
});
// Reference schema containing an ARRAY of itemDetailSchemes within the property item_details
const referenceSchema = new schema.Entity("references", {
item_details: [itemDetailSchema]
});
// Order schema containing an ARRAY of reference schemes within the property references
const orderSchema = new schema.Entity("orders", {
references: [referenceSchema]
});
const result = normalize(data, orderSchema);
console.dir(result);
This would be the result after normalizing.
Object
entities:
itemDetails: {100: {…}, 200: {…}}
orders: {3939393: {…}}
products: {443: {…}, 8080: {…}}
references: {123: {…}}
result: 3939393

How can I access all elements with a particular attribute in graphQL?

I have some json data in file called countryData.json structured as so:
{
"info":"success",
"stats":
[{
"id":"1",
"name":"USA",
"type":"WEST"
},
//...
I'm using graphQL to access this data. I have created an object type in the schema for countries using the following:
const CountryType = new GraphQLObjectType({
name: "Country",
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
type: { type: GraphQLString },
})
});
I want to write a query that will allow me to access all of the elements of this array that have a certain "name" value(There can be multiple with the same name). I've written the following query, but it only returns the first match in the array:
const RootQuery = new GraphQLObjectType({
name:"RootQueryType",
fields:{
country: {
type: CountryType,
args: { type: { name: GraphQLString } },
resolve(parent, args){
return _.find(countryData.stats, {name: args.name});
}
}
}
});
The "_" comes from const _ = require('lodash');
Also, how can I just get every single item in the array?
I have not recreated the code, therefore I can not check if it would be executed correctly. This is code, that should work in my opinion (without trying). If you want to return array of elements you need to implement https://lodash.com/docs/#filter. Filter will return all objects from stats, which match the argument name. This will return correctly inside resolver function, however, your schema needs adjustments to be able to return array of countries.
You need probably rewrite the arguments as follows as this is probably not correct. You can check out how queries or mutation arguments can be defined https://github.com/atherosai/express-graphql-demo/blob/feature/2-json-as-an-argument-for-graphql-mutations-and-queries/server/graphql/users/userMutations.js. I would rewrite it as follows to have argument "name"
args: { name: { type: GraphQLString } }
You need to add GraphQLList modifier, which defines, that you want to return array of CountryTypes from this query. The correct code should look something like this
const RootQuery = new GraphQLObjectType({
name:"RootQueryType",
fields:{
country: {
type: CountryType,
args: { name: { type: GraphQLString } },
resolve(parent, args){
return _.find(countryData.stats, {name: args.name});
}
},
countries: {
type: new GraphQLList(CountryType),
args: { name: { type: GraphQLString } },
resolve(parent, args){
return _.filter(countryData.stats, {name: args.name});
}
}
}
});
Now if you call query countries, you should be able to retrieve what you are expecting. I hope that it helps. If you need some further explanation, I made the article on implementing lists/arrays in GraphQL schema as I saw that many people struggle with similar issues. You can check it out here https://graphqlmastery.com/blog/graphql-list-how-to-use-arrays-in-graphql-schema
Edit: As for the question "how to retrieve every object". You can modify the code in resolver function in a way, that if the name argument is not specified you would not filter countries at all. This way you can have both cases in single query "countries".

Resources