React unable to rerender after setState using Fetch - json

I'm at my wits end. I'm a coding novice trying to use .map() to iterate through JSON data and display it in a card on React.
I fetch the data under componentDidMount() and use setState to assign it. This works completely fine on another page.
However, on this page I am trying to iterate through the 'projects' array on this object, but whenever I try to .map() into the products array I get errors.
Even with a simple console.log I get errors.
I think this has to do with asynchronous fetching but all the questions I see address this with setState(). I don't know what to do.
Here is my JSON object:
{
"_id": "59dac308b9267fbcb5d2de32",
"name": "The Jewelry Counter",
"description": "Limited edition fine jewelry, handcrafted in the USA",
"image": "/public/images/tjclogo.png",
"__v": 0,
"products": [
{
"_id": "59dada32b9267fbcb5d2de37",
"name": "Opal and cubic zirconia stacking ring set",
"price": 80,
"description": "A three ring stacking set that features an arced ring with an opal and cz, one with just glowing cz's and one with a single solitaire prong set opal.",
"img": "/shopping-cart-app/public/images/ringset.png",
"quantity": 2,
"__v": 0,
"keywords": [
"ring",
"opal",
"cubic zirconia",
"cz",
"jewelry",
"womens",
"jewelry counter"
]
},
{
"_id": "59dadae1b9267fbcb5d2de38",
"name": "Moonstone Ear Jackets",
"price": 140,
"description": "Four teardrop shaped glowing moonstones are prong set and attach to your ear in a simple three piece process that makes it look as though they are floating on your ears.",
"img": "/shopping-cart-app/public/images/moonearrings.png",
"quantity": 4,
"__v": 0,
"keywords": [
"earrings",
"moonstone",
"jewelry",
"womens",
"jewelry counter"
]
},
{
"_id": "59dadb79b9267fbcb5d2de39",
"name": "Horizontal Pyrite Necklace",
"price": 52,
"description": "A horizontal bar of hand crushed pyrite is attached to a brass bar on a 16\" brass chain.",
"img": "/shopping-cart-app/public/images/pyritenecklace.jpg",
"quantity": 1,
"__v": 0,
"keywords": [
"necklace",
"pyrite",
"jewelry",
"womens",
"jewelry counter"
]
},
{
"_id": "59dadcfbb9267fbcb5d2de3a",
"name": "Purple Tourmaline Promise Rings",
"price": 48,
"description": "Faceted purple tourmaline prong set on an 18K yellow gold vermeil band. This simple ring is perfect for stacking.",
"img": "/shopping-cart-app/public/images/ring.jpg",
"quantity": 1,
"__v": 0,
"keywords": [
"ring",
"tourmaline",
"jewelry",
"womens",
"jewelry counter"
]
}
]
}
I have the following code:
import React, { Component } from 'react';
import StoreCard from '../../StoreCard';
import ProductCard from '../../ProductCard';
import Wrapper from '../../Wrapper';
import Header from '../../Header';
import StoreLogin from "../../StoreLogin";
import Store from "../../Store";
import './Shop.css';
class Shop extends Component {
constructor(props) {
super(props);
this.state = { storeInfo: []};
}
// state = {storeInfo: [],
// products: []
// };
componentDidMount() {
fetch('/stores/59dac308b9267fbcb5d2de32')
.then(res => res.json())
.then((storeInfo) => {this.setState({ storeInfo: storeInfo })})
}
render() {
console.log(this.state.storeInfo) // works,displays the entire JSON object after beig called twice in the console.
console.log(this.state.storeInfo.name); // WORKS, same as above
console.log(this.state.storeInfo['products'][1]['name']); //DOES NOT WORK - ERRORS
// console.log(this.state.storeInfo.products[1].name); //DOES NOT WORK - ERRORS
return (
<div>
<Header location="Search all stores"/>
<Wrapper>
<StoreLogin
id={this.state.storeInfo._id} // works
userName={this.state.storeInfo.name} // works
// productName={this.state.storeInfo.products[1].name} //does not work
>
</StoreLogin>
</Wrapper>
<h1>Shop</h1>
</div>
);
}
}
export default Shop;
When I uncomment the 'console.logs' and the 'productName' in the storeLogin component one at a time, I get 3 errors:
Uncaught TypeError: Cannot read property '1' of undefined
Then
proxyConsole.js:54 The above error occurred in the <Shop> component:
in Shop (created by Route)
in Route (at App.js:22)
in div (at App.js:19)
in Router (created by BrowserRouter)
in BrowserRouter (at App.js:17)
in App (at index.js:6)
Consider adding an error boundary to your tree to customize error handling behavior.
You can learn more about error boundaries at https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html
And then
Uncaught TypeError: Cannot read property '1' of undefined
again.

The reason is data you are trying to access storeInfo['products'][1]['name'] is not yet available in the render function initially.So you need to check if data is present.For that what you can do is
render() {
console.log(this.state.storeInfo.name); // WORKS
//Move this console inside if
if ( this.state.storeInfo.products && this.state.storeInfo.products.length) {
console.log(this.state.storeInfo['products'][1]['name']);
console.log(this.state.storeInfo.products[0].name);
}
return (
<div>
<Header location="Search all stores"/>
<Wrapper>
<StoreLogin
id={this.state.storeInfo._id} // works
userName={this.state.storeInfo.name} // works
productName={this.state.storeInfo.products && this.state.storeInfo.products.length &&
this.state.storeInfo.products[0].name} //<-- Same here
>
</StoreLogin>
</Wrapper>
<h1>Shop</h1>
</div>
);
}

The problem is that, you are having a fetchRequest in the componentDidMount function and then setting the state, however your render is called before it and since you try to access this.state.storeInfo.products[0].name, this.state.storeInfo.products is undefined and hence you get an error, do a check before using it
return (
<div>
<Header location="Search all stores"/>
<Wrapper>
<StoreLogin
id={this.state.storeInfo._id} // works
userName={this.state.storeInfo.name} // works
productName={this.state.storeInfo.products && his.state.storeInfo.products[0]?
this.state.storeInfo.products[0].name: ''}
>

Related

mapping nested object as props to custom component

I have an app where the user will search for a term and they will see the results rendered. The results, in this case, are from a nested JSON object. I have a component called CompanyInfoList that passes props to Results component that renders the JSX. The props are employee, date, tax, and balance. I tried to map within a map in the component, but it did not work. My goal is to get access to the details data, how do I do this. The files to look at are CompanyInfoList and Results. The data is loaded in via axios in CompListContext
In the CompSearch comp when you enter "ABC" nothing will happen, because I am not accessing the details data from the JSON obj. This is what I need help in doing.
Here is the mongo DB JSON object (pasted from PostMan):
"data": {
"details": {
"employee": "person1",
"date": "test date",
"tax": "test tax",
"balance": "22"
},
"company": "TEST-ABC",
"_id": "60dba9fe7641a44d40364c1f",
"__v": 0
}
Here is my code
Given the company array object shape:
{
details: {
employee: "person1",
date: "test date",
tax: "test tax",
balance: "22"
},
company: "TEST-ABC",
_id: "60dba9fe7641a44d40364c1f",
__v: 0
}
You are filtering by the element's company property, and when mapping the filtered results in CompanyInfoList you need to access the details property, i.e. result.details.employee.
const CompanyInfoList = ({ filtered }) => {
const fltr = filtered.map((result) => (
<Results
key={result.details.id}
employee={result.details.employee}
date={result.details.date}
tax={result.details.tax}
balance={result.details.balance}
/>
));
return <>{fltr}</>;
};

Using react to fetch one item from json

So I got this problem.
I am trying to fetch a local json file from my project. The json file is stored in the src folder.
This is my json file
[
{
"name": "Sunsssset Beach",
"email": "info#sunsetbeach.com",
"image": "https://images.unsplash.com/photo-1439130490301-25e322d88054?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1489&q=80",
"price": 85,
"maxGuests": 18,
"lat": 60.393388,
"lng": 5.22872,
"description": "Get ready for some amazing sunsets as you sip a cocktail and watch dolphins play in the harbour below.",
"selfCatering": true,
"createdAt": "2020-09-04T09:07:10.367Z",
"id": "5f5203bedc17b0a4b302f211"
},
{
"name": "The Hideaway",
"email": "info#hideaway.com",
"image": "https://images.unsplash.com/photo-1551906993-c8b38a6ab201?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=666&q=80",
"price": 70,
"maxGuests": 2,
"lat": 60.425168,
"lng": 5.358141,
"description": "This secluded wilderness cabin is the perfect spot for a restful and cosy getaway.",
"selfCatering": true,
"createdAt": "2020-09-04T09:07:10.370Z",
"id": "5f5203bedc17b0a4b302f213"
},
{
"name": "Rest Easy",
"email": "management#resteasy.com",
"image": "https://images.unsplash.com/photo-1512552288940-3a300922a275?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=751&q=80",
"price": 120,
"maxGuests": 14,
"lat": 60.396779,
"lng": 5.235602,
"description": "Need some time off from your busy life to relax and unwind? Choose Rest Easy for the complete relaxation experience you desire.",
"selfCatering": false,
"createdAt": "2020-09-04T09:07:10.370Z",
"id": "5f5203bedc17b0a4b302f212"
}
]
And here is my project file
function Establishment() {
const [establishment, setEstablishment] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("jsonfile")
.then((response) => {
// check if response returns ok
if (response.ok) {
return response.json();
} else {
setErrorHandle(true);
}
})
.then((data) => {
setEstablishment(data);
})
.catch((err) => {
console.log(err);
setErrorHandle(true);
})
.finally(() => setLoading(false));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Now I want to be able to press a button do display all of these items, and by clicking on one item, it should display only that item in a different page. How do I do that?
Let me know if there is anything else you need to solve this problem :)
Firstly if the JSON data is in a local in your file system then you do not need fetch or axios
Why you did not use Axios or Fetch?
Axios or Fetch use to get or post data into the server. In our case, we read on the local folder file so that we use map().
Note: most important when using Axios and Fetch we need URL parameter compulsory
Now what you are to do is:
1. Firstly Import the file wherever you want to use:
import data from "../static/data/myData.json";
2. Now you want to map through that Json file:
<div>
{
data.map(myData, index)=>{
return <div key={myData.id}>
<h1>{myData.name}</h1>
<h3>{myData.email}</h3>
</div>
}
}
</div>
So that is how you want to handle this situation obviously structuring the content the way you desire not with h1's and h3's like I did

How is the ipcRenderer argument JSON object losing data when passed from an Angular 7 service?

I am having a strange issue with an Angular 7.1.1 and Electron 4.1.4 project.
Data Flow:
Angular Component "Report Builder" collects report configuration options from a FormGroup and FormControl validated form and sends data to docx-templater.service
User Button triggers createReport() function
When submitting options for a complete report, the createReport() function calls dataService's fnGetCompleteControlList() which returns properly configured JSON asynchronously.
with a .then() function after the async data retrieval, the createReport() function combines the output directory which is part of the configuration form and sends both to the docx-templater.service's createCompleteDocument() function. Once the promise is returned it updates the UI.
Angular Service "docx-templater"'s createCompleteDocument function passes the data and folder values to the ipcRenderer.send for the electron "writeCompleteDocument" channel and returns a promise.
In my main.ts, I have an ipcMain.on for the "writeCompleteDocument" channel that passes the data to a write-docx function for processing that data into a word document.
Problem:
When the data gets to my write-docx function it is missing a sub array of objects that are essential to the export process.
I have verified that the data is perfect in the Chrome Developer Tools console of electron at the moment just before it sends the data to the docx-templater.service and just before that service sends it to the ipcRenderer (meaning my data service and Report Builder functions are working as designed). When I check the data in the main.ts by saving the data off to a JSON file it is missing the controls sub array within the second object of the JSON only. The controls sub array shows up in the first object as expected.
I will note that what is coming out of the ipcMain function is a properly formed JSON file so it has really just excluded the "controls" sub array and is not truncating due to memory or buffer limits or anything like that.
report-builder.component.ts
createReport() {
if (this.reportBuilderFG.get('allControls').value) {
this.db.fnGetCompleteControlList()
.then((groups: Group[]) => {
this.word.createCompleteDocument(groups, this.reportBuilderFG.get('folder').value + '\\filename.docx')
.then(() => {
this.openSnackBar(this.reportBuilderFG.get('folder').value + '\\filename.docx created successfully');
});
});
} else {
// Do other stuff
}
docx-templater.service.ts
createCompleteDocument(data, folder: string): Promise<boolean> {
return new Promise(resolve => {
console.log(data) <=== Data is perfect here.
ipcRenderer.send('writeCompleteDocument', {data: data, folder: folder});
resolve();
});
}
main.ts
import { writeCompleteDocument } from './node_scripts/write-docx';
ipcMain.on('writeCompleteDocument', (event, arg) => {
fs.writeFileSync("IPCdata.json", arg.data); // <==== Part of the data is missing here.
writeCompleteDocument(arg.data, arg.folder);
});
Good Data Example (some keys and objects excluded for brevity)
[
{
"name": "General Security",
"order": 1,
"subgroups": [
{
"_id": "GOV",
"name": "Governance",
"order": 1,
"controls": [
{
"group": "GS",
"subgroup": "GOV",
"active": true,
"printOrder": 1,
"name": "This is my GS control name",
"requirements": [
{
"id": "SA01",
"active": true,
"order": 1,
"type": "SA",
"applicability": [
"ABC",
"DEF",
"GHI"
],
},
{ ... 3 more }
],
"_id": "GSRA-03",
"_rev": "1-0cbdefc93e56683bc98bae3a122f9783"
},
{ ... 3 more }
],
"_id": "GS",
"_rev": "1-b94d1651589eefd5ef0a52360dac6f9d"
},
{
"order": 2,
"name": "IT Security",
"subgroups": [
{
"_id": "PLCY",
"order": 1,
"name": "Policies",
"controls": [ <==== This entire sub array is missing when exporting from IPC Main
{
"group": "IT",
"subgroup": "PLCY",
"active": true,
"printOrder": 1,
"name": "This is my IT control name",
"requirements": [
{
"id": "SA01",
"active": true,
"order": 1,
"type": "SA",
"applicability": [
"ABC",
"DEF",
"GHI"
],
}
],
"_id": "GSRA-03",
"_rev": "1-0cbdefc93e56683bc98bae3a122f9783"
}
}
],
"_id": "IT",
"_rev": "2-e6ff53456e85b45d9bafd791652a945c"
}
]
I would have expected the ipcRenderer to pass a JSON exactly as it is to the ipcMain.on function, but somehow it is trimming part of the data. I have even tried strigifying the data before sending it to the renderer and then parsing it on the other side but that did nothing.
Could this be an async thing? I am at a loss of where to go next to debug and find what idiot mistake I made in the process.
Also, I realize that the above data flow seems overly complex for what I am doing, and that I can probably do it easier, but it makes sense (kinda) for the way the whole application is structured so I am going to go with it if I can squash this bug.
Looks like your createCompleteDocument() function is set up incorrectly. A quick search showed me that ipcRenderer is an async function, but you are responding to it (almost) synchronously.
You have the following, which is (probably) incorrect (actually it's definitely incorrect, because you've typed typed the return as Promise<boolean> when it is Promise<void>):
createCompleteDocument(data, folder: string): Promise<boolean> {
return new Promise(resolve => {
ipcRenderer.send('writeCompleteDocument', {data: data, folder: folder});
resolve();
});
}
ipcRenderer#send() is async but you are calling resolve() immediately afterwards without waiting for the function to resolve. This probably explains why adding the setTimeout() is fixing the problem for you. Looking at the ipcRenderer docs, the following probably does what you want:
createCompleteDocument(data, folder: string): Promise<Event> {
return new Promise(resolve => {
ipcRenderer.once('writeCompleteDocument', resolve);
ipcRenderer.send('writeCompleteDocument', {data: data, folder: folder});
});
}
Looks like the callback is passed an Event object.
Another option would be to simply replace ipcRenderer#send() with ipcRenderer#sendSync() in your original code, but as pointed out in that method's documentation :
Sending a synchronous message will block the whole renderer process, unless you know what you are doing you should never use it.
Making use of ipcRenderer#send() and ipcRenderer#once() is almost definitely the way to go.
Seperately, you can clean up the code by switching to async/await functions. For example:
async createReport(): Promise<void> {
if (this.reportBuilderFG.get('allControls').value) {
const groups: Group[] = await this.db.fnGetCompleteControlList();
await this.word.createCompleteDocument(
groups,
this.reportBuilderFG.get('folder').value + '\\filename.docx'
);
// Unclear if this function is actually async
await this.openSnackBar(
this.reportBuilderFG.get('folder').value +
'\\filename.docx created successfully'
);
} else {
// Do other stuff
}
}
I was able to solve this by adding a 1000 ms timeout after my fnGetCompleteControlList() data pull in the report-builder.component.ts. It seems like I have a lot more work todo with learning async functions. :-(
report-builder.component.ts
createReport() {
if (this.reportBuilderFG.get('allControls').value) {
this.db.fnGetCompleteControlList()
.then((groups: Group[]) => {
setTimeout(() => {
this.word.createCompleteDocument(groups, this.reportBuilderFG.get('folder').value + '\\filename.docx')
.then(() => {
this.openSnackBar(this.reportBuilderFG.get('folder').value + '\\filename.docx created successfully');
});
}, 1000);
});
} else {
// Do other stuff
}

How to display item of nested json array in component using Angular 7

How can I display "empName" and other details of this JSON in my table component in loop?
I'm using a third party API which provides a nested JSON object in return when I send employerID to the API URL.
After subscribing I'm storing the response in a var "AllPostedJobs"
{
"status": "All Jobs",
"result": [
{
"_id": "5c90fd3cfc7f3b0017803319",
"job_title": "Web Designer",
"job_desc": "test description ...",
"location": "Mangalore",
"experiance": "Freshers",
"job_type": "Full-Time",
"salary_package": "3L",
"job_keywords": "Photoshop, Illustrator",
"email_id": "hr#shreemithra.com",
"employerID": "5c7e99c2a7a9eb00174de2b2",
"company_name": "Shreemithra Designs",
"AppliedStudentDetails": [
{
"_id": "5c9393c1a918d60017de7e55",
"empName": "Anup",
"empID": "5c939375a918d60017de7e53",
"job_title": "Web Designer"
}
],
"__v": 1
},
{
"_id": "5c913570cb78a100177ab23a",
"job_title": "Full Stack Developer",
"job_desc": "htjhsv dhsd jh jds fjshgdfkhsdmhfd;lw eiwiwemsd. This is a sample job description.",
"location": "Pune",
"experiance": "2 Years",
"job_type": "Part-Time",
"salary_package": "8L - 10L PA",
"job_keywords": "Angular, Node JS, React, HTML5. CSS3",
"email_id": "info#shreemithra.com",
"employerID": "5c7e99c2a7a9eb00174de2b2",
"company_name": "Shreemithra Designs",
"AppliedStudentDetails": [
{
"_id": "5c9393c9a918d60017de7e56",
"empName": "Anup",
"empID": "5c939375a918d60017de7e53",
"job_title": "Full Stack Developer"
},
{
"_id": "5ca60fa5ba17730017182ca8",
"empName": "Amit Pateriya",
"empID": "5c7795acfd39640017ca4c37",
"job_title": "Full Stack Developer"
}
],
"__v": 2
}
]
}
The simplest way is when you receive data from api then send it to a
function then apply multiples loop alter your data by add keys in
front of data and that values for example
var data = [{"id":1, "name":"smith","applicant":{"roll": 32,"class":10}}];
data[0].applicantRoll = data[0].applicant.roll;
data[0].applicantClass = data[0].applicant.class;
now you can apply *ngfor easily, try this.
You can bind display data by binding controls in an HTML template to properties of your component.
The easiest way to display a component property is to bind the property name through interpolation. With interpolation, you put the property name in the view template, enclosed in double curly braces:{{AllPostedJobs.status}}.
<div id="result-container" *ngFor="let record of AllPostedJobs.result">
<another-component [record]= "record"></another-component>
</div>
Or depending on your need you can hand over entire result data to another-component.
Now your another-component, should have #Input defined to handle the incoming data:
export class AnotherComponent {
#Input() record: Array<any>;
In your another-component template:
<div *ngFor="let student of record?.AppliedStudentDetails">
<span>{{student.empName}}</span>
<span [innerText]="student.job_title"></span>
</div>

typeahead nested json object

I am new to Ember and JSON. I want to parse a JSON object that is below with typeahead library
and access nested object values by searching their keys.
I have this Json format:
return [
{
"id": 1,
"category_name": "Supermarket",
"category_description": "SUPER MARKET",
"image_url": "",
"merchants": [
{
"name": "CARREFOUR",
"id": 12,
"merchant_type_id": 1,
"merchant_type_description": "Gold",
"merchant_redeption_rate": 0.002500,
"image_url": "https://jpg",
"branches": [
{
"id": 123456,
"latitude": 37.939483,
"area": "ΑΓ. ΔΗΜΗΤΡΙΟΣ",
"zip": "12345"
},
{
"id": 4567890,
"longitude": 23.650622,
"area": "ΑΓ. ΙΩΑΝΝΗΣ ΡΕΝΤΗΣ",
"zip": "12345"
}
]
},
{
"name": "CAFCO",
"id": 13,
"merchant_type_id": 3,
"merchant_type_description": "None",
"merchant_redeption_rate": 0.002500,
"image_url": "https:.jpg",
"branches": [
{
"id": 127890,
"latitude": 38.027870,
"area": "ΠΕΡΙΣΤΕΡΙ",
"zip": "12345"
}
]
}
]
},
{
"id": 2,
"category_name": "Πολυκαταστήματα",
"category_description": "ΠΟΛΥΚΑΤΑΣΤΗΜΑ",
"image_url": "",
"merchants": [
{
"name": "AGGELOPOYLOS CHR.",
"id": 15,
"merchant_type_id": 2,
"merchant_type_description": "Silver",
"merchant_redeption_rate": 0.002500,
"image_url": "https://www.nbg.gr/greek/retail/cards/reward-programmes/gonational/PublishingImages/aggelopoulos.jpg",
"branches": [
{
"id": 234780,
"latitude": 35.366118,
"longitude": 24.479461,
"address": "ΕΘΝ. ΜΑΚΑΡΙΟΥ 9 & ΕΛ. ΒΕΝΙΖΕΛΟΥ 1",
"area": "Ν. ΦΑΛΗΡΟ",
"zip": "12345"
}
]
}
]
}
];
--------------------------Updated----------------------------
For example, i want to search using typeahead the name of merchants and when the letter we write to search matches the name of merchants it will appear the corresponding category_name and backwards.
Example -> when i keyboard the s it will appear :
Category : Supermarket,
Name: CARREFOUR
Name: CAFCO
And the same output on the dropdown of search when i keyboard the letter c.
Any help?
New Jsbin example
The simplest way (in my mind) to get this to work is to create a computed property that will contain an array of latitudes. But how do we get there?
To get to latitude, you need to go through array of merchants and then array of branches. Being that this will be across multiple elements, you are going to end up with "array of arrays" type data structure, which is annoying to deal with. So, to simplify this, we can create a simple flatten function as follows:
flatten: function(origArray){
var newArr = [];
origArray.forEach(function(el) {
el.forEach(function(eachEl){
newArr.push(eachEl);
});
});
return newArr;
},
In addition to our function above, Ember already provides us with many other useful functions that can be used on arrays (see here). One of those is mapBy(property) which transforms an array into another array only keeping the values of the property we specified.
So, to create a lats (for latitudes) property, we can just do this:
lats: function(){
var merchantsArr = this.get('model').mapBy('merchants');
merchantsArr = this.flatten(merchantsArr);
var branchesArr = merchantsArr.mapBy('branches');
branchesArr = this.flatten(branchesArr);
return branchesArr.mapBy("latitude").compact();
}.property('model')
Above, I am basically using mapBy, flatten (see above) and compact which
Returns a copy of the array with all null and undefined elements removed.
Once you have the lats property with all the necessary data, the rest is easy.
Your call to component becomes:
{{x-typeahead data=lats name='category_name' selection=myColor}}
Note lats instead of model you originally were passing into the component.
And now, to access the value of data property in the component, you do
`this.get('data')`
which you can just pass in as the source like so:
source: substringMatcher(self.get('data'))
Working solution here
Update
Updating my answer based on your updated question.
OK, so this is getting a little more complicated. You now need more than just one property (latitude) from the object. You need category_name and merchant name.
In addition to mapBy, which just grabs one property out of array, Ember also has map which lets you transform the array into pretty much anything you want to:
lats: function(){
var merchantsArr = this.get('model').map(function(thing){
var category_name = thing.category_name;
return thing.merchants.map(function(merchant){
return {
"name": merchant.name,
"category": category_name
};
});
});
merchantsArr = this.flatten(merchantsArr);
return merchantsArr;
}.property('model')
The code above looks complicated, but it's basically just returning an array of top level objects' merchants accompanied by category_name. Since this is an array of arrays, we will need to flatten it.
Then, inside the component, we need to keep in mind that we are not just passing in an array of strings, but rather we are passing in an array of objects. Therefore, we need to look through object's properties (name and category) for a match
$.each(strs, function(i, str) {
if (substrRegex.test(str.name) || substrRegex.test(str.category)) {
matches.push(str);
}
});
Lastly, to actually display both category and merchant name, you need to tell Typeahead how to do that:
templates: {
suggestion: Handlebars.compile('<p>{{name}} – {{category}}</p>')
}
Working solution here