React JS, display Data from Nested json - reactjs

I'm having trouble displaying data from this nested json
I can't display all items in the items box
I've tried some solution but still not work,
since my json format is kinda different.
Json data
{
"data": [
{
"pattern": "Right",
"Color": "blue",
"Weight": "50",
"items": {
"chair": {
"location": "c1-2a",
"quantity": "10",
"available": true
},
"table": {
"location": "c1-2c",
"quantity": "5",
"available": false
}
}
},
{
"pattern": "Left",
"Color": "green",
"Weight": "12",
"items": {
"mouse": {
"location": "c2-2a",
"quantity": "29",
"available": true
},
"headphones": {
"location": "c1-2e",
"quantity": "50",
"available": false
},
"monitor": {
"location": "c1-2e",
"quantity": "2",
"available": false
}
}
}
]
}
React Code
class UsersTable extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: []
}
}
componentDidMount() {
fetch("/test")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
data: result.data
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const {error, isLoaded, data} = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<table id="example" className="table table-striped table-bordered">
<thead>
<tr>
<th>Pattern</th>
<th>Color</th>
<th>item</th>
<th>location</th>
</tr>
</thead>
<tbody>
{data.map(item =>
(
<React.Fragment>
<tr>
<td>{item.pattern}</td>
<td>{item.color}</td>
<td>{item.items}</td> <======= I cant loop all items
<td>{item.location}</td>
</tr>
</React.Fragment>
))}
</tbody>
</table>
);
}
}
}
Expected result
----------------------------------------------------------
|Patern|Color | Item |Location |
----------------------------------------------------------
|Right |blue |chair,table |c1-2a,c1-2c |
|left |green |mouse,headphones,monitor|c2-2a,c1-2e,c1-2e|
Error result
Error: Objects are not valid as a React child (found: object with keys {chair, table}). If you meant to render a collection of children, use an array instead.
Cheers!

items is a object in your case, first you have extract values from items object by using items.map() or so. Because items is a object hence you are getting below error.
Error: Objects are not valid as a React child (found: object with keys {chair, table}). If you meant to render a collection of children, use an array instead.

Before returing from map fuction, first map over items and create string of items object.
{data.map(item => {
var items = item.items;
var itemList = "";
items.forEach(item => {
itemList = itemList + item + " ,";
})
return (
<React.Fragment>
<tr>
<td>{item.pattern}</td>
<td>{item.color}</td>
<td>{itemList}</td>
<td>{item.location}</td>
</tr>
</React.Fragment>
)))}

You trying to loop over an object, use [key, value] of Object.entries
<td>{for let [key, value] of Object.entries(item.items) { .... }</td>

Related

How to access certain element of nested objects in react

I'm struggling to take certain value out of an API. I have no trouble mapping over the parts that I can immediately access using items.value, but when I can't get some of the more nested info. I'm specifically trying to access the value of "quantity" inside of pricing.
Here's my code
import "./StoreDisplay.css"
class StoreDisplay extends Component {
constructor(props) {
super(props)
this.state = {
error: undefined,
isLoaded: false,
items: []
}
}
componentDidMount() {
this.getData();
}
getData() {
fetch(url)
.then((res) => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result,
});
console.log(result)
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
);
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div id = "storeDisplay">
<ul className = "container">
{items.map(item => (
<li key = {item.title}>
<div className = "bundleName"> {item.title} {item.pricing.quantity}</div><img src = {item.asset} className = "img"></img>
</li>
))}
</ul>
</div>
);
}
}
}
Sample part of JSON from API:
[
{
"title": "100-Pack Bundle",
"desc": "",
"tag": "",
"purchaseLimit": 1,
"isAvailable": true,
"expireTimestamp": 1662538288,
"shopType": "specials",
"originalPrice": 10500,
"pricing": [
{
"ref": "Apex Coins",
"quantity": 6700
}
],
"content": [
{
"ref": "weapon_charm_charm_apex_asset_v22_misc_pathfinderemoji01",
"name": "MRVN Monitor",
"quantity": 1
},
{
"ref": "pack_cosmetic_rare",
"name": "Apex Pack",
"quantity": 100
}
],
"offerID": "220823_100-pack_bundle",
"asset": "https:\/\/shop-cdn.mozambiquehe.re\/dl_store_s14_0_bund_epic_100pack_a189.png"
},
It looks like item.pricing is actually an array of objects. From here you have a couple of choices, depending on what you want to do.
item.pricing will only ever have one element.
In this case, you can just take the first element:
<div className = "bundleName"> {item.title} {item.pricing[0].quantity}</div>
You want to list all the quantities
<div className = "bundleName"> {item.title} {item.pricing.map(pricing => pricing.quantity).join(" ")}</div>
or
<div className = "bundleName"> {item.title} {item.pricing.map(pricing => pricing.quantity).join(", ")}</div>
You want to have the sum of all quantities
<div className = "bundleName"> {item.title} {item.pricing.map(pricing => pricing.quantity).reduce((a, b) => a + b, 0)}</div>

React Hook useState Is Returning Undefined

I am trying to fill a table with data from an API.
UPDATED*************
import React, { useMemo, useState, useCallback, useEffect } from "react";
import {
AppLayout,
Button,
Box,
Form,
SpaceBetween,
Grid,
} from "#affn/awsui-components-react/polaris";
import "#affn/awsui-global-styles/polaris.css";
import "./styles/landing-page.scss";
import { appLayoutLabels, externalLinkProps } from "./common/labels";
import Picture1 from "./resources/engineLogos/bric_team_dark_backgroung(1).svg";
import {
ExternalLinkItem,
Navigation,
InfoLink,
} from "./commons/common-components-BRIC";
function BricPage() {
const Content = ({ navigationOpen }) => {
//Constants needed by the form ------------------------------------------------
const refreshPage = () => {
window.location.reload();
};
// The comment (i.e conversation) id must be unique
const conversationId = uuidv4();
//Function to handle submit click and create SIM
const handleClick = (
title1,
description1,
) => () => {
console.log(title1);
console.log(description1);
};
const [GetSimIDs, setGetSimIDs] = React.useState([]); //Which is the impacted region?
// Using useEffect to call the API once mounted and set the data
useEffect(() => {
window.harxny.api
.invokeProxy(
"/sit/ises?sort=createDate desc&q=status:(Open) containingFolder:(45-b5b9-4829-8b87-489053f9bb42)",
{
method: "GET",
// SIM integration is only possible from the 'beta' and 'corp' stages.
stage: "corp",
headers: {
"Content-Type": "application/json",
},
}
) //api finishes here
.then((xhr) => {
//response is captured here
//var SIMID = JSON.parse(xhr.response).id;
console.log(xhr.responseText);
const data = JSON.parse(xhr.response);
//const data = xhr.response;
console.log(data);
console.log(data.totalNumberFound);
setGetSimIDs(data);
console.log(GetSimIDs);
});
}, []);
//End of Constants -------------------------------------------------------------
console.log(GetSimIDs);
return (
<Box margin={{ bottom: "l" }}>
<div className="center-form">
<Box>
<Grid
gridDefinition={[
{
colspan: { xl: "2", l: "2", s: "5", xxs: "10" },
offset: { l: "2", xxs: "1" },
},
{
colspan: { xl: "2", l: "3", s: "5", xxs: "10" },
offset: { s: "0", xxs: "1" },
},
]}
>
<div className="custom-home-main-content-area">
<SpaceBetween size="l">
<Form
actions={
<SpaceBetween direction="horizontal" size="xs">
<Button onClick={refreshPage} variant="link">
Reset Form
</Button>
<Button
variant="primary"
onClick={handleClick(
title1,
description1,
)}
ariaLabel="Submit"
>
Submit
</Button>
</SpaceBetween>
}
>
</Form>
</SpaceBetween>
</div>
{/* Table goes here */}
{console.log(GetSimIDs)}
<tbody>
<tr>
<th>title</th>
<th>Id</th>
</tr>
{GetSimIDs.documents.map((item, i) => (
<tr key={i}>
<td>{item.title}</td>
<td>{item.id}</td>
</tr>
))}
</tbody>
</Grid>
</Box>
</div>
</Box>
);
};
const [navigationOpen, setNavigationOpen] = React.useState(false);
return (
<AppLayout
disableContentPaddings={true}
content={<Content />}
navigation={<Navigation activeHref="#/" />}
navigationOpen={navigationOpen}
onNavigationChange={({ detail }) => setNavigationOpen(detail.open)}
toolsHide={true}
ariaLabels={appLayoutLabels}
/>
);
}
export default BricPage;
The state GetSimIDs is updated successfully with data like this:
{
"documents": [
{
"assignedFolder": "4a37-416c-8531-",
"extensions": {
"tt": {
"impact": 5,
"category": "EiC",
"type": "IBug",
"item": "Macro",
"assignedGroup": "EiC",
"justification": [],
"minImpact": 5,
"status": "Assd"
}
},
"watchers": [
{ "id": "bric-primary#amazon.com", "type": "email" },
{ "id": "sssesuni#amazon.com", "type": "email" },
{ "id": "raaishwa#amazon.com", "type": "email" },
{ "id": "dipchakr#amazon.com", "type": "email" }
],
"customFields": {
"number": [{ "id": "fte_saving", "value": 0 }],
"date": [
{ "id": "delivery_date", "value": "2022-05-17T15:43:49.825Z" }
],
"string": [
{ "id": "category_of_the_request", "value": "Other" },
{ "id": "region_of_impact", "value": "NA" },
{ "id": "tool_type", "value": "Excel Macro" },
{
"id": "impacted_tool",
"value": "Tickets Helper"
}
]
}
},
{
"title": "Issue or Bug - Global Wizard - NA",
"assignedFolder": "416c-8531-37fa3a701712",
"watchers": [{ "id": "bprimary#a.com", "type": "email" }],
"customFields": {
"number": [{ "id": "fte_saving", "value": 0 }],
"date": [
{ "id": "delivery_date", "value": "2022-05-13T02:22:46.751Z" }
],
"string": [
{ "id": "category_of_the_request", "value": "Other" },
{ "id": "region_of_impact", "value": "NA" },
{ "id": "tool_type", "value": "Excel Macro" },
{ "id": "impacted_tool", "value": "Global Wizard" }
]
}
}
],
"totalNumberFound": 2,
"searchLogMessages": [],
"startToken": ""
}
So I tried to update the table with the following code:
<tbody>
<tr>
<th>title</th>
<th>Id</th>
<th>status</th>
</tr>
{GetSimIDs.map((documents, i) => (
<tr key={i}>
<td>{documents.title}</td>
<td>{documents.id}</td>
<td>{documents.status}</td>
</tr>
))}
</tbody>
But I keep getting an error in line
{GetSimIDs.map((documents, i) => (
Saying that TypeError: s is undefined
Any idea as of why it seems not getting the data from the hook?
I am very new to react so all feedback would be appreciated.
Thanks
Luis V.
This is a common problem with async data. The state is initially undefined, and is filled in later after the request has completed. There will always be at least one render before your data is loaded.
You can fix this in several ways, but the simplest would be to just initialize the state to an empty array:
const [GetSimIDs, setGetSimIDs] = React.useState([]);
Now the state is always defined, and you can map over it even before the data is loaded.
Another option would be to check the data before mapping it:
{GetSimIDs && GetSimIDs.map((documents, i) => (
Data returned from the API seems to be an object with a property documents which is an array. Either you can
setGetSimIDs(data.documents)
OR
GetSimIDs.documents.map(...)
Update (Codesandbox): Seems you're also missing a few null checks due to which there are errors. Initially the object + array are empty so we can't use the map function. Only when data has successfully loaded we can render the rows.
I have used your data set & made a mock api.
export default function App() {
const [GetSimIDs, setGetSimIDs] = useState({});
useEffect(() => {
axios
.get("https://getsimids.free.beeceptor.com/my/api/path")
.then((res) => {
setGetSimIDs(res.data);
});
}, []);
return (
<div className="App">
<table>
<tbody>
<tr>
<th>title</th>
<th>Id</th>
<th>status</th>
</tr>
{GetSimIDs.documents &&
GetSimIDs.documents.length > 0 &&
GetSimIDs.documents.map((documents, i) => (
<tr key={i}>
<td>{documents.title}</td>
<td>{documents.id}</td>
<td>{documents.status}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
First, you can log the value of GetSimIDs and GetSimIDs before the map function.
console.log(GetSimIDs, GetSimIDs.documents)
GetSimIDs.map...
It will probably be undefined as at the first load of the page it is not initialized.
I would suggest you set the const [GetSimIDs, setGetSimIDs] = React.useState([]); instead of empty.
For your further problem with react hooks (useEffect) I would suggest you setState outside the useEffect. Extract the method that fetches data e.g getData and then use it inside useEffect()
useEffect(()=>{getData()},[])

How to pass object array as props to a custom component in react native?

I have fetched some data from an API as a JSON array in componentDidMount method as below.
componentDidMount() {
return fetch('http://..someAPI../product/')
.then(res => res.json())
.then(resJson => {
this.setState({
isLoading: false,
dataSource: resJson,
});
var objects = this.state.dataSource;
for (var i = 0; i < objects.length; i++) {
console.log('Item Name: ' + objects[i].productName);
}
})
.catch(err => {
console.log(err);
});
}
In here I get console.log output as I want. Now I want to pass the array in a loop as a prop to a custom component, but it gives error.
My render method looks like this
return (
<View>
<Content>
{this.state.dataSource.map(item => {
<Product Name={item.productName} price={item.price}/>;
})}
</Content>
</View>
);
My original Json object looks like this
[
{
"category": [
"Food",
"Bread",
"Bun"
],
"_id": "1",
"productName": "Sausage bun",
"price": 70,
"details": "test product",
},
{
"category": [
"Food",
"Bread",
"Bun"
],
"_id": "2",
"productName": "Fish Pastry",
"price": 50,
"details": "test product",
}
]
I want to pass these data to display the products as a loop. How can I do this? Thank you in advance!
Since data loading is asynchronous you probably have uninitialised state.
As a safer coding practice you could something like
{this.state.dataSource && this.state.dataSource.map(item => {
return <Product Name={item.productName} price={item.price}/>;
})}
Depending on your webpack configuration , you can also use optional chaining https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
{this.state?.dataSource?.map(item => {
<Product Name={item.productName} price={item.price}/>;
})}
Also,
initialize your state
this.state = {
dataSource: []
}

Map through Array object stored in state

Im trying to access data from an API and render the results. Im storing each object in a separate state and im trying to map through each state, however this throws the following Error.
My code is:
state = {
events: [],
items: [],
isLoading: true
}
// fetchData = () => {
componentDidMount() {
const url = 'http://www.mocky.io/v2/5c8673f0340000981789c0da'
axios.get(url).then(response => {
this.setState({ items: response.data, isLoading: false, events: response.data[0] })
console.log(this.state.events)
})
}
render() {
return(
<div>
<div>
<h1>26/2</h1>
<ul>
{ this.state.events.map(event => (<li>{event.activity}</li>)) }
</ul>
</div>
<div>
<h1>27/2</h1>
<ul>
</ul>
</div>
</div>
)
}
}
API structure:
[
{
"id": 1,
"activity": "lunch",
"startDate": "2019-02-26 12:00",
"endDate": "2019-02-26 13:00",
"location": "Lagerhuset"
},
{
"id": 2,
"activity": "meeting",
"startDate": "2019-02-26 22:00",
"endDate": "2019-02-27 07:00",
"location": "online"
},
{
"id": 3,
"activity": "meeting",
"startDate": "2019-02-26 10:00",
"endDate": "2019-02-26 12:00",
"location": "Lagerhuset"
}
]
Now im wondering if what I'm trying to achieve even is possible? And if so, what am I doing wrong? Thanks.
items contain the array of items. events just contain the first item which is an object. Hence the map fails.
Either use :
{this.state.items.map(event => (
<li>{event.activity}</li>
))}
or this :
<li>{this.state.events.activity}</li>

Populate table with JSON data and variable columns

I have the following JSON data:
{
"languageKeys": [{
"id": 1,
"project": null,
"key": "GENERIC.WELCOME",
"languageStrings": [{
"id": 1,
"content": "Welcome",
"language": {
"id": 1,
"key": "EN"
}
}]
}, {
"id": 2,
"project": null,
"key": "GENERIC.HELLO",
"languageStrings": [{
"id": 2,
"content": "Hej",
"language": {
"id": 2,
"key": "DK"
}
}, {
"id": 5,
"content": "Hello",
"language": {
"id": 1,
"key": "EN"
}
}]
}, {
"id": 3,
"project": null,
"key": "GENERIC.GOODBYE",
"languageStrings": []
}]
}
I want that converted into a table where the columns are variable.
The table output should look like the following:
------------------------------------------------
| Key | EN | DK | SE | [...] |
| GENERIC.WELCOME | Welcome | | | |
| GENERIC.HELLO | Hello | Hej | | |
| GENERIC.GOODBYE | | | | |
------------------------------------------------
As you can see, the table is dynamic in both rows and columns, and I am struggling to figure out how to map the correct data in each of the "EN", "DK", "SE" [...] fields to the correct column since they are not neccessarily in order when I get them in the JSON response from the API.
I got the following render function so far:
private static renderLanguageKeysTable(languageKeys: ILanguageKey[], languages: ILanguage[]) {
return <table>
<thead>
<tr>
<td>Key</td>
{languages.map(language =>
<td key={language.id}>{language.key}</td>
)}
</tr>
</thead>
<tbody>
{languageKeys.map(languageKey =>
<tr key={languageKey.id}>
<td>{languageKey.key}</td>
{languages.map(language =>
<td key={language.id}>
</td>
)}
</tr>
)}
</tbody>
</table>
;
}
This works as it should, the only part missing is the data in the columns.
I have tried various variations of filter and map but nonw of them worked out the way I wanted them to.
I am using ReactJS and writing in typescript (es2015)
To clarify a bit:
The columns will always be defined by the API, and the rows cannot have an ID pointing to a column that is not there since they are related in the backend.
It may however happen that some rows does not have all the columns, in such case they should just be blank
I ended up using a different approach from what was suggested (after a good nights sleep and some thinking)
Basically, I created a new component for each individual cell, resulting in the following render on the table side of the code:
private static renderLanguageKeysTable(languageKeys: ILanguageKey[], languages: ILanguage[]) {
return <table>
<thead>
<tr>
<th>Key</th>
{languages.map(language =>
<th key={language.id}>{language.key}</th>
)}
</tr>
</thead>
<tbody>
{languageKeys.map(languageKey =>
<tr key={languageKey.id}>
<td>{languageKey.key}</td>
{languages.map(language =>
<Cell language={language} languageKey={languageKey} key={language.id} />
)}
</tr>
)}
</tbody>
</table>
;
}
And the following code for rendering each cell:
import * as React from "react";
export class Cell extends React.Component {
render() {
let string: any;
if (this.props.languageKey && this.props.languageKey.languageStrings) {
let languageString =
this.props.languageKey.languageStrings.find((i: any) => i.language.id === this.props.language.id);
if (languageString === null || languageString === undefined) {
string = "";
} else {
string = languageString.content;
}
} else {
string = "";
}
return <td>
{string}
</td>;
}
props: any;
}
const findDistinctLang = (langKeys) => {
let langString = []
langKeys.forEach((element) => {
if(element.languageStrings.length !== 0) {
langString = [...langString, ...element.languageStrings]
}
})
const langArr = []
langString.forEach((element) => {
if (langArr.indexOf(element.language.key) === -1) {
langArr.push(element.language.key)
}
})
return langArr
}
class Table extends React.Component {
state = {
"languageKeys": [{
"id": 1,
"project": null,
"key": "GENERIC.WELCOME",
"languageStrings": [{
"id": 1,
"content": "Welcome",
"language": {
"id": 1,
"key": "EN"
}
}]
}, {
"id": 2,
"project": null,
"key": "GENERIC.HELLO",
"languageStrings": [{
"id": 2,
"content": "Hej",
"language": {
"id": 2,
"key": "DK"
}
}, {
"id": 5,
"content": "Hello",
"language": {
"id": 1,
"key": "EN"
}
}]
}, {
"id": 3,
"project": null,
"key": "GENERIC.GOODBYE",
"languageStrings": [{
"id": 2,
"content": "Hej",
"language": {
"id": 2,
"key": "DK"
}
},{
"id": 5,
"content": "XYZ",
"language": {
"id": 7,
"key": "XYZ"
}
}]
}]
}
getContentName = (languageSet, langName) => {
return _.find(languageSet.languageStrings, function(o) { return o.language.key === langName })
}
render() {
const lanKeyArr = findDistinctLang(this.state.languageKeys)
return ( <
table >
<
thead >
<
tr >
<
td > Key < /td> {
lanKeyArr.map((lang) => {
return ( < td > {
lang
} < /td>)
})
} <
/tr> <
/thead> <
tbody >
{
this.state.languageKeys.map((languageSet) => {
return(
<tr>
<td>{languageSet.key}</td>
{[...lanKeyArr].map((element, index) => {
const contentObj = this.getContentName(languageSet, element)
return (
<td>{contentObj && contentObj.content || ""}</td>
)
})
}
</tr>
)
})
}
<
/tbody> < /
table >
)
}
}
ReactDOM.render(<Table />,document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I have implemented based on the test data you provided,
Note: you can make it more clean , just giving you an idea by this example
You can parse the object and render the table according to the expected rendering.
Note, have minimal experience using ReactJS and have not tried TypeScript
let languages = {"languageKeys":[{"id":1,"project":null,"key":"GENERIC.WELCOME","languageStrings":[{"id":1,"content":"Welcome","language":{"id":1,"key":"EN"}}]},{"id":2,"project":null,"key":"GENERIC.HELLO","languageStrings":[{"id":2,"content":"Hej","language":{"id":2,"key":"DK"}},{"id":5,"content":"Hello","language":{"id":1,"key":"EN"}}]},{"id":3,"project":null,"key":"GENERIC.GOODBYE","languageStrings":[]}]};
const table = document.querySelector("table");
const thead = table.querySelector("thead").querySelector("tr");
const tbody = table.querySelector("tbody");
Object.values(languages.languageKeys).forEach(({key, languageStrings}) => {
// check if `languageStrings` array has `.length` greater than `0`
if (languageStrings.length) {
languageStrings.forEach(({content, language:{key:lang}}) => {
console.log(key, content, lang);
// use block scopes
{
// check if the `lang` is already appended to `<thead>`
if (![...thead.querySelectorAll("td")].find(({textContent}) => textContent === lang)) {
let td = document.createElement("td");
td.textContent = lang;
thead.appendChild(td);
}
}
{
// append `key`
let tr = document.createElement("tr");
let tdKey = document.createElement("td");
tdKey.textContent = key;
tr.appendChild(tdKey);
// append `content`
let tdContent = document.createElement("td");
tdContent.textContent = content;
tr.appendChild(tdKey);
tr.appendChild(tdContent);
tbody.appendChild(tr);
// append a `<td>` for placing `<td>` in correct column
// not an optimal approach, adjust if necessary
if ([...thead.querySelectorAll("td")].findIndex(el => el.textContent === lang) === tr.children.length) {
tr.insertBefore(document.createElement("td"), tr.lastElementChild);
};
}
})
} else {
// handle empty `languageStrings` array
let tr = document.createElement("tr");
let tdKey = document.createElement("td");
tdKey.textContent = key;
tr.appendChild(tdKey);
tbody.appendChild(tr);
}
})
<table>
<thead>
<tr>
<td>Key</td>
</tr>
</thead>
<tbody>
</tbody>
</table>

Resources