using flatmap to make nested service call with parameter - json

I am making a service call returning data from json file with a bunch of items but after get all the items i need to make another service call to get the contents of each of the items. I am using a flatmap but i am having some trouble on how to pass in a parameter - when i try it becomes underlined in the code as an error.
This is my data call:
getItems(){
this.itemService.getItemsData().flatMap(
data => {this.items = data;
error => this.errorMessage = <any>error;
return this.itemService.getItemContent();
}).subscribe(data => {
this.itemContent = data;
});
}
when i try passing into...getItemContent(this.items.contentUri) it gives me an error.
getItemsData(){
return this._http.get(url)
.map((res:Response) => res.json())
.map((obj) => Object.keys(obj).map((key)=>{return obj[key]}))
.catch(this.handleError);
}
getItemContent(uri){
return this._http.get(uri)
.map((res:Response) => res.json())
.catch(this.handleError);
}
How should i properly do this so when i get items i could also make a call to get the items contents based on a parameter?
here is a sample of the json structure:
{
Item 1: {
title:....
id:....
content:{
uri:"link"
}
}
}
UPDATE:
getItems(){
this.itemService.getItemsData().flatMap(
data => {this.items = data;
for(let x of data){
var url = x.content.uri;
this.observables.push(this.itemService.getInnerItemData(url));
}
return Observable.forkJoin(this.observables);
}).subscribe(data => {
this.itemsContent = data;
});
}
HTML:
<div *ngFor="let item of items">
{{item.title}}
<div *ngFor="let content of itemsContent">
{{content.infoText}}
</div>
</div>
now within my display the item.title is showing appropriately as expected but the content within each item is showing up as an array of [object][object] and seems like all the itemsContent is showing up for each item and it is not specified with each itemsContent with belonging item.

Use forkJoin to make parallel requests.
getItems(){
this.itemService.getItemsData().flatMap(
data => {
this.items = data;
var observables = [];
for(let x of data){
var url = x.content.uri;
observables.push(this.itemService.getItemContent(url));
}
return Observable.forkJoin(observables);
}).subscribe(data => {
console.log(data);
});
}
Then in your subscribe you will see an array of response for your item contents.
Update
In your view, you can track the items' index and display that indexed items content like this:
<div *ngFor="let item of items; let i = index;">
{{item.title}}
<dic>
{{itemsContent[i]?.infoText}}
</div>
</div>

Related

React Beautiful DnD, multiple columns inside single droppable

I am trying to have a grid column layout, (2 columns) inside a single droppable container. The project is for an online menu where you can create a menu item, which goes into a droppable container, then you can drag that onto the menu that will be displayed to the user. So there is currently two columns. However the style of the menu demands two columns. Currently I am assigning different classNames to the mapped columns so I can make one of them grid but its pretty messy. Maybe there is a way I can hardcode the droppable instead of map them and run the map on the lists themselves inside each of the hardcoded droppables? Sorry if this is confusing, it sure is for me.
'results' is API data that is initially mapped into savedItems array where newly created menu items will go. Later on menuItems array will pull from the database as well. Right now just trying to have better styling control over the different droppables.
you can see where im assigning different classNames to the droppable during the mapping and its really not a reliable option.
//drag and drop states
const [state, setState] = useState({
menuItems: {
title: "menuItems",
items: []
},
savedItems: {
title: "savedItems",
items: results
}
})
useEffect(() => {
setState({ ...state, savedItems: { ...state.savedItems, items: results } })
}, [results])
// console.log("state", state)
console.log("dummy data", dummyArry)
// updating title graphql mutation
const [elementId, setElementId] = useState(" ");
const updateTitle = async () => {
//api data
const data = await fetch(`http://localhost:8081/graphql`, {
method: 'POST',
body: JSON.stringify({
query: `
mutation {
updateMenu(menuInput: {_id: ${JSON.stringify(elementId)},title: ${JSON.stringify(inputValue)}}){
title
}
}
`
}),
headers: {
'Content-Type': 'application/json'
}
})
//convert api data to json
const json = await data.json();
}
//drag end function
const handleDragEnd = (data) => {
console.log("from", data.source)
console.log("to", data.destination)
if (!data.destination) {
// console.log("not dropped in droppable")
return
}
if (data.destination.index === data.source.index && data.destination.droppableId === data.source.droppableId) {
// console.log("dropped in same place")
return
}
//create copy of item before removing from state
const itemCopy = { ...state[data.source.droppableId].items[data.source.index] }
setState(prev => {
prev = { ...prev }
//remove from previous items array
prev[data.source.droppableId].items.splice(data.source.index, 1)
//adding new item to array
prev[data.destination.droppableId].items.splice(data.destination.index, 0, itemCopy)
return prev
})
}
const columnClass = [
"menuItems-column",
"savedItems-column"
]
let num = 0
return (
<>
<div className='app'>
{results && <DragDropContext onDragEnd={handleDragEnd}>
{_.map(state, (data, key) => {
return (
<div key={key} className='column'>
<h3>{data.title}</h3>
<Droppable droppableId={key}>
{(provided, snapshot) => {
return (
<div
ref={provided.innerRef}
{...provided.droppableProps}
className={columnClass[num]}
// className="droppable-col"
><span className='class-switch'>{num++}</span>
{data.items.map((el, index) => {
return (
<Draggable key={el._id} index={index} draggableId={el._id}>
{(provided) => {
return (
<div className='element-container'
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div contentEditable="true">
{el.title}
</div>
</div>
)
}}
</Draggable>
)
})}
{provided.placeholder}
</div>
)
}}
</Droppable>
</div>
)
})}
</DragDropContext>}
</div>
</>
)
}

Javascript (es6) possible to destructure and return on a single line?

I'm curious if it's possible to return a destructured object on the same line it was destructured.
Current (working) examples:
using 2 lines
const itemList = json.map((item) => {
const { id, title } = item;
return { id, title };
});
1 line but not destructured
const itemList = json.map((item) => {
return { id: item.id, title: item.title }; // This also requires repeating the field name twice per instance which feels hacky
});
Is it possible to condense the body down to one line ?
example (doesn't work)
const itemList = json.map((item) => {
return { id, title } = item;
}
Destructure the callback parameters, and return an object:
const itemList = json.map(({ id, title }) => ({ id, title }))

How to save an array of JSON objects (with nested objects) to state in React

I'm trying to save an array of JSON objects returned from an API call to state in React (so that I can use the data to render a table). I'm getting the error Error: Objects are not valid as a React child (found: object with keys {street, suite, city, zipcode, geo}). If you meant to render a collection of children, use an array instead.
I can't figure out how to fix this. It looks like the JSON is being stored inside an array as it should be. However, there are also nested objects inside the objects that may be causing an issue, for example:
address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
Any assistance would be much appreciated. Here's my code below:
let tableData = []
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(data => {
tableData = data
props.addItem(tableData)
})
Here's the addItem function:
addItem(item) {
this.setState(function(prevState) {
return {
tables: [...prevState.tables, item]
}
})
}
UPDATE
Here's how I am rendering the data:
App.js:
render() {
return (
<div>
{this.state.tables.map(item => {
return (<TableComponent key={item} data={item} />)
})}
</div>
)
}
TableComponent.js:
class TableComponent extends React.Component {
constructor(props){
super(props);
this.getHeader = this.getHeader.bind(this);
this.getRowsData = this.getRowsData.bind(this);
this.getKeys = this.getKeys.bind(this);
}
getKeys = function(){
return Object.keys(this.props.data[0]);
}
getHeader = function(){
let keys = this.getKeys();
return keys.map((key, index)=>{
return <th key={key}>{key.toUpperCase()}</th>
})
}
getRowsData = function(){
let items = this.props.data;
let keys = this.getKeys();
return items.map((row, index)=>{
return <tr key={index}><RenderRow key={index} data={row} keys={keys}/></tr>
})
}
render() {
return (
<div>
<table>
<thead>
<tr>{this.getHeader()}</tr>
</thead>
<tbody>
{this.getRowsData()}
</tbody>
</table>
</div>
);
}
}
const RenderRow = (props) =>{
return props.keys.map((key, index)=>{
return <td key={props.data[key]}>{props.data[key]}</td>
})
}
The error message threw me off here because it made it seem like the issue was in saving the objects to state. However, as was pointed out in the comments, the error happened during rendering. To solve the issue I changed RenderRow to the following:
const RenderRow = (props) =>{
return props.keys.map((key, index)=>{
return <td key={props.data[key]}>{typeof props.data[key] === "object" ? JSON.stringify(props.data[key]) : props.data[key]}</td>
})
}
Specifically, the piece that I changed is to first check whether a specific element is an object, and if it is, to use JSON.stringify() to convert it to a string before rendering it to the screen.

React--Div exists, but is empty & more problems

I'm using the code below to pull in a list of data from a JSON file in order to populate a webpage with News. However, with what I have, the div is empty when I inspect it, and I'm not sure why. When I attempt other solutions, I get errors or the same output.
const newsList = labNewsJson['news']
class News extends Component {
render() {
const news = newsList.map((newsItem) => {
<div>{newsItem}</div>
});
return (
<div className='container'>
<h1>Lab News</h1>
<div>{news}</div>
</div>
);
}
}
export default News;
You need to add a return to your map function.
const news = newsList.map((newsItem, index) => {
return <div key={index}>{newsItem.title}</div>
});
When you are using {}, map function does not return anything. You have two options:
1- Try to use () instead of {}:
const news = newsList.map((newsItem) => (
<div>{newsItem}</div>
))
2- Return the item in every iteration:
const news = newsList.map((newsItem) => {
return <div>{newsItem}</div>
})

Object from Observable then Array from Observable inside a foreach. how to order it?Asynchronous Angular 4/5

Here is my problem.
I'm running a method that sends me a json (method = myTableService.getAllTables ()), to create an object (object = this.myTables).
Then I execute the method for each, for each element of this.myTables I execute a new request (request = this.myTableService.getTableStatut (element.theId)).
I retrieve data from a new json to create an object (object = myTableModel).
Each result will be added to this.myTableListProvisory.
The problem is the order of execution.
It execute the console.log before the end of the for each...
This.myTableListProvisory.length and this.myTableList.length return 0.
How to wait for the end of the for each run before running the console.log?
Thank you
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
console.log("this.myTableList.length : " + this.myTableList.length);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
console.log("this.myTableListProvisoire.length : " + this.myTableListProvisoire.length);
})
return this.myTableListProvisoire;
}
Result of console.log
this.myTableListProvisoire.length : 0
this.myTableList.length : 0
UPDATE
I have simplified the code ... I put it in its entirety for the understanding. What I need is to sort the array after it is done. The problem is that I don't know how to use a flatMap method in a query inside a foreach ... I have temporarily placed the sort method inside the subscribe which is a bad solution for the performance. That's why I want to do my sort after the creation of the array. Thank you
export class MyTableComponent implements OnInit {
myTables: any;
statut: any;
myTableModel: MyTableModel;
myTableList: Array<MyTableModel>;
myTableListProvisoire: Array<MyTableModel>;
i: number;
j: number;
myTableModelProvisoire: MyTableModel = null;
constructor(public myTableService: MyTableService) { }
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
for (this.j = 0; this.j < this.myTableListProvisoire.length; this.j++) {
for (this.i = 0; this.i < this.myTableListProvisoire.length - 1; this.i++) {
if (this.myTableListProvisoire[this.i].getTableNumber() > this.myTableListProvisoire[(this.i + 1)].getTableNumber()) {
this.myTableModelProvisoire = this.myTableListProvisoire[this.i];
this.myTableListProvisoire[this.i] = this.myTableListProvisoire[(this.i + 1)];
this.myTableListProvisoire[(this.i + 1)] = this.myTableModelProvisoire;
}
}
}
}, err => {
console.log(err);
})
}, err => {
console.log(err);
})
return this.myTableListProvisoire;
}
}
Well Observables are asynchronous actions and will be executed after finishing the current execution block. So when the js engine comes to your
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
it will only create a subscription, but the code inside of it will be executed after all the other code in the block. So that's why your console.log is being executed before you get any data. So you need to place it inside the .subscribe block to see the. I think there can be a better solution to get the data, but I don't know the structure of the app, so I can't advice. If you create an example on https://stackblitz.com/ I could probably help you out with a better solution.