Normalizing my JSON response using normalizr with redux - reactjs

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

Related

Adding object into an array of a document in mongoDB

Scenario:
There are two collections, one contains products and the other contains reviews. Both products and reviews collection have a field called "productID". Task is to gather all fields from individual review document and add them to the corresponding product document.
My approach:
I am trying to collect all the products and then iterate through each one.
This is to individually extract productID and pass it over to find all reviews that match with the productID.
All the reviews are then stored in a variable.
4.Finally, I try to update the current product by pushing the extracted fields from documents that match with productID
Code:
var cursor = db.products.find({}, {_id: 0})
while (cursor.hasNext()) {
var currDoc = cursor.next();
var pID = currDoc.productID;
var revs = db.reviews.find({productID: pID}, {
_id: 0,
stars: 1,
reviewTitle: 1,
reviewText: 1,
})
db.products.update({ productID: pID }, { $push: { reviews: revs } })
Expect:
products {
productID: ##,
productName: "asdfghjkl",
productPrice: ##.##,
reviews:
[
{
stars: 1,
reviewTitle: "Avoid",
reviewText: "Not very great"
}
]
}
Actual:
BSONError: cyclic dependency detected
Convert your PID to string. it will return as an object which needs to be changed as string
db.products.find().forEach(function(doc){
var reviews = db.reviews.find({'productId':str(doc._id)}, {
_id: 0,
stars: 1,
reviewTitle: 1,
reviewText: "Not very great",
})
db.products.update({ "_id": doc._id },{ "$set": { "reviews": reviews } });
})

Graphql mutation query : how to access the data elements

const MUTATION_QUERY = gql`
mutation MUTATION_QUERY(
$name: bigint!
) {
insert_name(
objects: {
name: $name
}
) {
returning {
id
name
}
}
}
`;
const [onClick, { error, data }] = useMutation<{}, {}>(MUTATION_QUERY, {
variables: {
name: 1234,
},
});
My mutation query is inserting name in my table and autogenerating the id. On console logging the data variable I can view the fields id and name in the data object. But I am not able to access them them individually. How can I console.log "id". Thank you.
the console.log(data) looks like : {insert_name: {...}}
which expands to :
insert_name:
returning: Array(1)
0: {id: 1, name: 1234}
length: 1
_proto_: Array(0)
_typename: "insert_name_mutation_response
You can access the fields of an object with .
For example, if your object looks like this -
data = {
id: 1,
name: 'Jane',
}
You can get just the id with data.id
This works no matter how many layers deep your object may go, so take this example -
data = {
person: {
id: 1,
name: 'Jane',
}
}
You could get the id of person with data.person.id.
console.log(data.insert_name.returning[0].id) will give you the id returned.
For it to work in typescript we need to change the query to add the return type of data
const [onClick, { error, data }] = useMutation<{ReturnInsertNameProps}, {}>(MUTATION_QUERY, {
variables: {
name: 1234,
},
});
interface ReturnInsertNameProps {
insert_name: ReturnQueryProps;
}
interface ReturnProps {
returning: MessageProps[];
}
interface NameProps {
id: number;
name: number;
}
We can also use onCompleted method provided in useMutation if we want to process the result of the query.

How to programmatically add to an array in database from frontend React

I basically have this route
app.post("/order/:orderId/:productId", async (req, res) => {
const result = await Order.findByIdAndUpdate(
req.params.orderId,
{
$push: {
products: req.params.productId
}
},
{ new: true }
);
res.send(result);
});
I have two collections, namely Product and Orders. The goal is to get, for instance, a particular Order with id(5ddfc649e1e9e31220ce6a16) , and post a product with id(5de02c4a0ed3160368b9a550) inside an Array field inside this Orders collection. In Postman, I can do that manually by just adding the ObjectIds in the URL like so:
Http://localhost:3000/orders/5ddfc649e1e9e31220ce6a16/5de02c4a0ed3160368b9a550.
and I get this Response :
{
"products": [
"5ddfb388b14c5b41e0607a5e",
"5de02c4a0ed3160368b9a550" // newly added Product
],
"_id": "5ddfc649e1e9e31220ce6a16",
"issuedBy": "issuedBy",
"collectedBy": "collectedBy",
"quantity": 123,
"createdAt": "2019-11-28T11:48:40.500Z",
"updatedAt": "2019-11-28T11:59:51.659Z",
"__v": 0
}
My challenge is, how do I do this programmatically from the UI(Reactjs) side?
// Product Schema
const mongoose = require("mongoose");
const ProductSchema = new mongoose.Schema(
{
name: String,
description: String,
price: Number,
quantity: Number,
supplier: String
},
{ timestamps: true }
);
module.exports = mongoose.model("Product", ProductSchema);
// Orders Schema
const mongoose = require("mongoose");
const OrderSchema = new mongoose.Schema(
{
issuedBy: String,
collectedBy: String,
quantity: Number,
products: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
required: true
}
]
},
{ timestamps: true }
);
module.exports = mongoose.model("Order", OrderSchema);
I would really appreciate any suggestion or a sample code snippet
I think what you are looking for is, you need to hit this API from your front end i.e. React app.
http://localhost:3000/orders/<order-id-here>/<product-id-here>
And then supply the dynamic value for your order id and product id.
You can hit api using fetch. More info
You can simply take some variables in your front end app. As I don't know how your order id and product id are being generated so you need to check on that and then something like,
let orderId = someValue
let productId = someOtherValue
let url = "http://localhost:3000/orders/"+orderId+"/"+productId
But I think the concern would be GET and POST method. Right now you have app.post but the params are in URL so instead you should go for GET method i.e. app.get

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.

Normalizr - How to handle nested entities that are already normalized

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.

Resources