I'm struggling to properly organize my Redux Store and my React components to properly deal with two-way nested data.
Suppose I have a post model and a user model. Let's take an abstracted example:
const user = {
"id": "1",
"name": "user 1",
"posts": [...] // list of post objects
}
const post = {
"id": "1",
"title": "post 1",
"user": user
}
The problem is that I cannot load this data like this because it will cause an infinite recursion error. I have to omit either the posts from the user or omit the user from the posts.
Here's what I ideally need:
I need to have a single post page that displays the post user with all his info (id, name) and the user's list of posts with the post info (id, title) in the same screen all at once.
I use normalizr to normalize the data.
How would I go about loading the data?
As per Redux docs (https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape) you should avoid nesting objects.
The solution here would be to treat the data like it is a database. That means that you should store ids instead of objects.
In your example:
const user = {
"id": "1",
"name": "user 1",
"posts": ["1", "2", ...] // list of post objects IDs
}
const post = {
"id": "1",
"title": "post 1",
"userId": "1"
}
Normally you would only save post_ids in the user, and user_id in the post. normalizer schemas can be configured to deal with those relations.
What is the incentive of adding the user key in post? Having shared data within two related data structures is redundant. The only data you should have in post is the relevant data and the minimum amount of information you need to properly associate the user with his/her posts. I would imagine you would want to have the string name of the user in post, or an id number of the user inside each post object
const user = {
"id": "1",
"name": "user 1",
"posts": [...] // list of post objects
}
const post = {
"id": "1",
"title": "post 1",
"user": "user 1"
}
EDIT:
From your comment, I would make an array of all users. But with a post key that is only the ids of posts that are associated with that user. And another array of only posts. As before, have an identifier to correlate the two. After that, parse your frontend on an as needed basis.
const users = [
{ name: 'andrew', id: 10, postIds: [1,3,23,30]},
// ...more users
]
const posts = [
{ name: 'a post', id: 23, userId: 10 }
{ name: 'another post', id: 3, userId: 10 }
{ name: 'a third post', id: 2, userId: 3 }
// ...more posts
]
Technically, userId is optional, but it can be a nice-to-have to be able to identify a user when inspecting individual posts
EDIT:
Wow, I just scrolled down and saw nordus has the exact same proposal, before me. While it's nice to see we're on the same page, make sure he/she gets credit if you like the idea ;).
Related
I have currently created a nodejs api where a user can send data about their career. They can send text and an uploaded image.
The data is sent as follows and I have been successful with sending via postman
{
"employee": "user",
"positionHeld": [{
"yearPostitionHeld": "1995 - 2000",
"positionHeldTitle": "Manager"
},
{
"yearPostitionHeld": "2000 - Present",
"positionHeldTitle": "Assocaite Manager"
}],
"address": "Company Address",
"responsibilities": ["Responsibility 1", "Responsibility 2", "Responsibility 3"]
}
I have created a front end page with ReactJS so the user can send the data as above by filling in input fields.
I set the user input via the useState Hook
const [formData, setFormData] = useState({
companyName: "",
year: "",
positionHeld: [
{
positionHeldYear: "",
positionHeldTitle: "",
},
],
address: "",
responsibilities: "",
});
via an onChange function
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
I also hanle the file upload
const [fileData, setFileData] = useState();
const handleFilechange = (e) => {
setFileData(e.target.files[0]);
};
I first tried to send the data as the formData but the file/image always appeared in req.body and not req.file and therefore I was unable to obtain the file path.
As a solution to the file issue I then create a new formData const completeFormData = new FormData(); and appending this
const year = formData.positionHeldYear;
const title = formData.positionHeldTitle;
setFormData({...formData, positionHeld: {positionHeldYear: year, positionHeldTitle: title}})
completeFormData.append("companyName", formData.companyName);
completeFormData.append("year", formData.year);
completeFormData.append("positionHeld", formData.positionHeld); // this needs to send the array of objects
completeFormData.append("address", formData.address);
completeFormData.append("responsibilities", formData.responsibilities);
completeFormData.append("image", fileData);
I am unsure if it is possible to send an array of objects as the new FormData such as the
"positionHeld": [{
"yearPostitionHeld": "1995 - 2000",
"positionHeldTitle": "Manager"
},
{
"yearPostitionHeld": "2000 - Present",
"positionHeldTitle": "Assocaite Manager"
}],
When creating the new form data as above when the req.body comes to the backend the positionHeld displays as positionHeld: [object, Object] when doing a console log
How is it best to manage sending data with nest array of object strings and image uploads?
I might have gone down the wrong route with this, so hoping someone can point me in the right direction to send body and a file to confirm as my object
{
"employee": "user",
"positionHeld": [{
"yearPostitionHeld": "1995 - 2000",
"positionHeldTitle": "Manager"
},
{
"yearPostitionHeld": "2000 - Present",
"positionHeldTitle": "Assocaite Manager"
}],
"address": "Company Address",
"responsibilities": ["Responsibility 1", "Responsibility 2", "Responsibility 3"]
}
One way of sending an array in FormData is to serialize it:
completeFormData.append("positionHeld", JSON.stringify(formData.positionHeld));
Then, on the backend, you can parse it with JSON.parse().
We did it with ReactJS(front) and ASP.Net core(back) So the best way to do it is using FormData, We use an object like below that contains an array of objects, each object includes an array of images too,
{
CommodityID:xxxxx,
TotalWeight:xxxx,
CostOfCommodity:xxxx,
Prerequisites:[{
ProductId: xxxx,
DeliveryWeight: xxxxx,
ReleasedImagesUrl: [
multiple images file
]
},{
ProductId: xxxx,
DeliveryWeight: xxxxx,
ReleasedImagesUrl: [
multiple images file
]
}]
}
Actually, we send each item of the array separately like Prerequisites[0].DeliveryWeight and this is the point (pairs of name/value). please pay attention to the letters that in our case first characters of items were capital (this is important too)
const formData = new FormData();
formData.append("CommodityID", this.state.commodityId);
formData.append("TotalWeight", this.state.totalWeight);
formData.append("CostOfCommodity",this.state.costOfCommodity);
for (let i = 0; i < this.state.prerequisitesListTemp.length; i++) {
formData.append(
`Prerequisites[${i}].ProductId`,
this.state.prerequisitesListTemp[i].productId
);
formData.append(
`Prerequisites[${i}].DeliveryWeight`,
this.state.prerequisitesListTemp[i].deliveryWeight
);
for (
let j = 0;j < this.state.prerequisitesListTemp[i].releasedImagesUrl.length;j++
) {
formData.append(
`Prerequisites[${i}].ReleasedImagesUrl`,
this.state.prerequisitesListTemp[i].releasedImagesUrl[j]
);
}
}
I am trying to create a status list from two separate JSON sources. The list will display general info from the first source, and show a status color based on the number of people in the second source.
The first source contains general data that will not be changing much (i.e. feed name, version, description) and likely called only two times a day. See code example below:
/metadata
{
data: [
{
"feedName": "Feed 1",
"version": "000001",
"location": "1234 Main Street, New York, New York"
"description": "This feed gives information on the number of people in Building A at a given time."
},
{
"feedName": "Feed 2",
"version": "000001",
"location": "1000 Broad Street, New York, New York"
"description": "This feed gives information on the number of people in Building B at a given time."
},
{
"feedName": "Feed 3",
"version": "000001",
"location": "1111 Governor Street, New York, New York"
"description": "This feed gives information on the number of people in Building C at a given time."
}
]
}
The second source contains data on each feed that will change very often. This source will be called more frequently; about every hour.
/customers
{
data: [
{
"metricName": "Feed 1",
"customerNumber": "10",
"time": "2012-10-03 15:30:00"
},
{
"metricName": "Feed 2",
"customerNumber": "5",
"time": "2012-10-03 15:30:00"
},
{
"metricName": "Feed 3",
"customerNumber": "15",
"time": "2012-10-03 15:30:00"
}
]
}
Where metricName and feedName are actually the same values.
I've only dealt with one JSON source per list before, where my Javascript would look like this:
$scope.metadataList = function(){
$http.get('/metadata')
.then(function(result) {
$scope.metadata = result.data.data;
});
}
and corresponding HTML would look like this:
<ul ng-repeat = "data in metadata | orderBy: ['feedName']">
<li> {{data.feedName}}, {{data.version}} </li>
</ul>
So my question is, how to I make async calls to each data source? How do I match them up to populate my list with both the metadata information and the customer information?
Update
Before anyone asks, I did try ng-repeat = "data in metadata.concat(customers)" where "customers" defines the second data source, (as shown in Ng-repeat datas from 2 json Angular) but that only appends to the end of the list ... not quite what I was going for.
Thank you in advance.
First, you can call the all() method from the $q service to resolve multiple promises, in that case the two requests.
// both promises calling your api to get the jsons
var promises = {
metadataPromise: $http.get('/echo/json/', {}),
customersPromise: $http.get('/echo/json/', {})
};
$q.all(promises).then(function(response) {
metadata = response.metadataPromise;
customers = response.customersPromise;
joinMetadataAndCustomers();
}, function(response) {
// in case any promise is rejected
});
After that, you check for every feedName the metricName which matches it using filter. In the end, you can merge both with angular.merge.
var joinMetadataAndCustomers = function() {
metadata.data.forEach(function(mtd) {
customers.data.filter(function(customer) {
return customer.metricName === mtd.feedName;
}).forEach(function(customer) {
$ctrl.metadataAndCustomers.push(angular.merge({}, mtd, customer));
});
});
}
Not sure if it's the best approach, but it works. Of course can be improved according to your needs. For example, if you only have one single match, the last forEach can be avoided.
Fiddle with an example: https://jsfiddle.net/virgilioafonsojr/tv1ujet0/
I hope it helps.
I have a JSON object that I need to post & save it using cakePHP 3.
First I have 4 tables as in this image
Let's say a supplier buys some products, so I need to fill a purchase record and the association table products_purchases, which will contain all the records of products bought during that one purchase.
I'll be using some JavaScript to dynamically generate some fields to be able to fill multiple products with quantities, prices.. during that purchase, and then store all the data in a JSON object.
So in the add.ctp view of purchase, I'll have this JSON object for exmaple :
$data = {
"reference": "V656413",
"supplier_id": 31,
"payment_id": 5,
"products_purchases": [
{
"product_id": 5566,
"purchase_id" 999,
"quantity": 4,
"price": 899
},
{
"product_id": 7865,
"purchase_id" 999,
"quantity": 6,
"price": 54
},{
"product_id": 434,
"purchase_id" 999,
"quantity": 8,
"price": 22
},
]
}
How can I POST this object using the form in the add.ctp file, and how can I handle it and save it as an entity with its associations in the PurchasesController?
The add method in the controller for now is as follow :
public function add()
{
$purchase = $this->Purchases->newEntity();
if ($this->request->is('post')) {
$purchase = $this->Purchases->patchEntity($purchase, $this->request->data);
if ($this->Purchases->save($purchase)) {
$this->Flash->success(__('The purchase has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The purchase could not be saved. Please, try again.'));
}
}
$this->set('_serialize', ['purchase']);
}
Thank you very much!
You have to manage proper associations with your models. visit below link where you can see how to save data with associations.
https://book.cakephp.org/3.0/en/orm/saving-data.html
I'm using Ydn-Db-Fulltext to allow users to search a local database of contacts in an HTML5 app. So far, when it comes to searching for names of people, it works great, is smart, and returns results instantly.
Here's an example of a contact object that contains an array of contact Methods:
{
"source": "COMPANY",
"ownerPin": "12345",
"name": "brian wilkins",
"dateUpdated": "2014-03-18T14:41:05.217Z",
"dateAdded": "2014-03-18T14:41:05.217Z",
"isFavorite": false,
"socialId": "54321",
"id": "1",
"deleted": false,
"edited": false,
"favorite": false,
"contactMethods": [
{
"id": "4321",
"contactKey": "12321",
"contactId": "1",
"value": "brian.wilkins#geemail.com",
"kind": "email",
"ownerPin": "12345",
"isPrimary": false
},
{
"id": "5432",
"contactKey": "2",
"contactId": "1",
"kind": "phone",
"ownerPin": "12345",
"isPrimary": false
},
{
"id": "23",
"contactKey": "333",
"contactId": "1",
"value": "112345",
"kind": "extension",
"ownerPin": "12345",
"isPrimary": false
}
]
}
To create the index on the "name" property, I setup the fullTextCatalog as follows:
fullTextCatalogs: [{
name: 'name',
lang: 'en',
sources: [
{
storeName: 'contacts',
keyPath: 'id',
weight: 1.0
}, {
storeName: 'contacts',
keyPath: 'name',
weight: 0.5
}
]
}],
stores: [
{
name: 'contacts',
keyPath: 'id',
autoIncrement: true
}
]
};
this.db = new ydn.db.Storage('thedatabase', db_schema);
I can search by name or by id (the key) and get a list of contacts that match. Little appears to be stored in memory. Every search queries the local backing indexedDB database.
The challenge is that I also want to be able to search based on email address and extension, which are stored in the contactMethods property inside an array of contactMethods. The "value" property is where we store the email address and/or extension depending on the contactMethod type.
I tried adding contactMethods as a secondary searchable object store, but this resulted in searches for "Brian" returning two results, both the contact containing the name, and the contactMethod containing the email address. Ideally, I'd want to take the contactId (foreign key to the contact) and use it to pull the actual contact object, but it seems like this could create very expensive overhead and negate the benefits of this great search tool.
Is there a way to index object properties that are not at the parent level? How can I approach this in a way that would scale and not eat up all of the resources?
this.db.get(entry.storeName, entry.primaryKey).done(function(x) {
this.textContent += ' [Full name: ' + x.name + ']'; // this is in the contact
this.textContent += ' [email: ' + x.value + ']'; // but this is in the contactMethod
}, span);
Is there a way to index object properties that are not at the parent level?
keyPath can refer to deep object property by using dotted notation. For example, you could specify contactMethods.value to index email, but unfortunately it does not work with array value - as in this case.
So, obvious choice is keeping contactMethods record in separate object store using parent-child relationship. Since ydn-db currently does not support embedded attribute in the schema, you will have to load all child records when loading parent object.
Alternatively, IndexedDB v2 may have virtual index generated by a function expression. You can use in ydn-db by generator in index schema, for example:
stores: [
{
name: 'contacts',
keyPath: 'id',
autoIncrement: true,
indexes: [{
name: '$emails',
multiEntry: true,
generator: function(record) {
return record.contactMethods.map(function(x) {return x.value};
})
}]
}
]
One thing to note though, the generated field $emails will appear when you load the data. It likely will be removed from the record so as to match with v2 spec.
We are using this generator index heavily in multiple projects, so I will fix bug.
Indexing id and email address in full text search is convenient, but does not make sense because phonetic base full text search will be index them as it is without normalization.
I'm working on an application that lets our security dispatchers update a page that contains current road and campus conditions. The backend is a nodejs/express stack with and the data is a simple JSON structure that looks something like this:
{
"campus": {"condition": "open", "status": "normal"},
"roads": {"condition": "wet", "status": "alert"},
"adjacentroads": {"condition": "not applicable", "status": "warning"},
"transit": {"condition": "on schedule", "status": "normal"},
"classes": {"condition": "on schedule", "status": "normal"},
"exams": {"condition": "on schedule", "status": "normal"},
"announcements" : "The campus is currently under attack by a herd of wild velociraptors. It is recommended that you do not come to campus at this time. Busses are delayed.",
"sidebar": [
"<p>Constant traffic updates can be heard on radio station AM1234. Traffic updates also run every 10 minutes on AM5678 and AM901.</p>",
"<p>This report is also available at <strong>555-555-1234</strong> and will be updated whenever conditions change.</p>"
],
"links": [
{
"category": "Transportation Links",
"links": [
{
"url": "http://www.localtransit.whatever",
"text" : "Local Transit Agency"
},
{
"url": "http://m.localtransit.whatever",
"text" : "Local Transit Agency Mobile Site"
}
]
},
{
"category": "Weather Forecasts",
"links": [
{
"url": "http://weatheroffice.ec.gc.ca/canada_e.",
"text" : "Environment Canada"
},
{
"url": "http://www.theweathernetwork.com",
"text" : "The Weather Network"
}
]
},
{
"category": "Campus Notices & Conditions",
"links": [
{
"url": "http://www.foo.bar/security",
"text" : "Security Alerts & Traffic Notices"
},
{
"url": "http://foo.bar/athletics/whatever",
"text" : "Recreation & Athletics Conditions"
}
]
},
{
"category": "Wildlife Links",
"links": [
{
"url": "http://velociraptors.info",
"text" : "Velociraptor Encounters"
}
]
}
],
"lastupdated": 1333151930179
}
I'm wondering what the best way of working with this data on the client side would be (e.g. on the page that the dispatchers use to update the data). The page is a mix of selects (the campus, roads, etc conditions), TinyMCE textareas (announcements and sidebar) and text inputs (links). I'm open to changing this data structure if necessary but it seems to me to work well. I've been looking at Backbone, and also Can.JS but I'm not sure if either of those are suitable for this.
Some additional information:
there's no need to update an individual item in the data structure separatly; I plan on POSTing the entire structure when it's saved. That said...
there's actually two different views, one for the dispatchers and another for their supervisors. The dispatchers only have the ability to change the campus, roads, etc conditions through drop-downs and furthermore can only change the "condition" key; each possible condition has a default status assigned to it. Supervisors can override the default status, and have access to the announcements, sidebar and links keys. Maybe I do need to rethink the previous point about POSTing the whole thing at once?
the supervisors need to be able to add and remove links, as well as add and remove entire link categories. This means that DOM elements need to be added and removed, which is why I'm thinking of using something like Backbone or Can.js instead of just writing some ghetto solution that looks at all the form elements and builds the appropriate JSON to POST to the server.
Suggestions welcomed!
CanJS works great with nested data. can.Model is inheriting can.Observe which allows you to listen to any changes in the object structure.
If you include can.Observe.Delegate you have even more powerful event mechanism (example from the docs):
// create an observable
var observe = new can.Observe({
name : {
first : "Justin Meyer"
}
})
var handler;
//listen to changes on a property
observe.delegate("name.first","set",
handler = function(ev, newVal, oldVal, prop){
this //-> "Justin"
ev.currentTarget //-> observe
newVal //-> "Justin Meyer"
oldVal //-> "Justin"
prop //-> "name.first"
});
// change the property
observe.attr('name.first',"Justin")