How can I destructure a JSON response into a table? - javascript

I am creating a functional component am fetching some data from an internal API and am wondering how I can make destructure the table rows into something a little less verbose. The following is the response I am getting from this API.
{
"data": [
{
"id": "cc134653-c463-4e79-8b9e-f52dfe02498e",
"type": "bottle",
"attributes": {
"name": "Luc Belaire Rare Luxe",
"price": "$29.99",
"image": "https://cdn11.bigcommerce.com/s-
7a906/images/stencil/1000x1000/products/10929/10518/Luc-Belaire-Rare-Luxe__73902.1555086630.jpg?c=2",
"sku": "813497005010",
"size": "750ML",
"origination": "France",
"varietal": "Sparkling Wine"
}
},
}
I am setting the state of the component like this.
const [bottles, setBottles] = useState([]);
useEffect(() => {
fetch('http://localhost:3000/api/v1/bottles?', { method: "GET" })
.then(response => response.json())
.then(data => setBottles(data.data));
});
This is how I am creating a table body in my component but am wondering if there is a better way to use the bottle.attributes.name and other attributes to make it more like {name}. How can I achieve this?
<tbody>
{bottles.map(bottle =>
<tr key={bottle.id}>
<td><img src={bottle.attributes.image} alt={bottle.attributes.name} height={150} width={100}/></td>
<td>{bottle.attributes.name}</td>
<td>{bottle.attributes.sku}</td>
<td>{bottle.attributes.price}</td>
<td>{bottle.attributes.size}</td>
<td>{bottle.attributes.origination}</td>
<td>{bottle.attributes.varietal}</td>
</tr>
)}
</tbody>

It will have to be a bit repetitive regardless - if you destructure the argument, you'll have to list out each individual property in the argument list:
{bottles.map(({ id, attributes: { image, name, sku, price, size, origination, varietal }}) =>
<tr key={id}>
<td><img src={image} alt={name} height={150} width={100}/></td>
<td>{name}</td>
<td>{sku}</td>
I'd prefer to just destructure to get the attributes, and then list attributes.name, etc:
<tbody>
{bottles.map(({ id, attributes }) =>
<tr key={id}>
<td><img src={attributes.image} alt={attributes.name} height={150} width={100}/></td>
<td>{attributes.name}</td>
<td>{attributes.sku}</td>
which is better than going through bottle each time.

You could make an array of the property names and use an inner map()
const attrs =['name', 'sku', 'price', 'size', 'origination', 'varietal']
<tbody>
{bottles.map(bottle =>
<tr key={bottle.id}>
<td><img src={bottle.attributes.image} alt={bottle.attributes.name} height={150} width={100}/></td>
{attrs.map(k => <td>{bottle.attributes[k]}</td>}
</tr>
)}
</tbody>

Related

Iterating over object and displaying as table - React.js

I have an object:
{
cities: {
MCR: "Manchester",
LDN: "London",
NYC: "New York"
}
}
and I want to iterate over these and display them in a table format which will show the city code and the long name next to it. Something like:
<tbody>
<tr>
<th>City Code</th>
<th>City Name</th>
</tr>
<tr>
{Object.entries(cities).map(([key, value]) => {
<>
<td>{key}</td>
<td>{value}</td>
</>
}
)}
</tr>
</tbody>
However this doesn't display anything and also shows an error when hovering over value:
Type 'unknown' is not assignable to type 'ReactNode'.ts(2322)
I'm quite new to React and wondering how best to display this information?
Thanks
let say you have an object as:
const obj = {
cities: {
MCR: "Manchester",
LDN: "London",
NYC: "New York"
}
};
It would be better to loop over cities and create a new tr so that It looks like a table
CODESANDBOX LINK
You want to map over the cities then you should return it from map as:
<tbody>
<tr>
<th>City Code</th>
<th>City Name</th>
</tr>
{ Object.entries( obj.cities ).map( ( [key, value] ) => {
return ( // PROBLEM SHOULD RETURN
<tr>
<td>{ key }</td>
<td>{ value }</td>
</tr>
);
}
) }
</tbody>
You need to return the result in each iteration. Also, each iteration would return a tr with a key prop:
{
Object.entries(obj.cities).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{value}</td>
</tr>
))
}
Note: since city is a key in an object, you'll need to access it as above
You are just missing the return in your map's callback function. Always remember to include a return in your arrow functions when using it like this: () => {}. It is easy to miss because ()=>() would return whatever you write in the second () but in the former example this is not true.

Am I passing headers wrong in SWR

It's be a while since I was doing any sort of coding and I am coming back to help a friend with the creation of a site. However jumping straight into the depend I am now stuck with SWR.
Problem
I am just trying to send a Authorization: Bearer header however upon the page loading SWR just shows it loading state? I am aware this could be a simple problem but a helpful answer would be amazing. I have also tried looking at dev tools etc and nothing related to SWR is there.
The API Site is built with Strapi V4 and I am aware they have changed there structre of the API response which could also be an issue for me however I am not aware of one yet, so if you spot one gladly let me know!
Code
import {
Table,
TableCaption,
Thead,
Tr,
Th,
Tbody,
Td,
Tfoot,
} from "#chakra-ui/react";
import useSWR from "swr";
import axios from "axios";
const fetcher = async (url) => {
const res = await axios.get(url, {
headers: {
"Authorization": `Bearer ${process.env.API_KEY}`,
},
});
return res.data;
};
export const BluForTable = () => {
const { data, error} = useSWR(
["https://api-skr.herokuapp.com/api/factions", fetcher]
);
if(error) return (
console.log(error),
(
<div>Failed to load {error.message}</div>
)
)
if(!data) return <div>loading...</div>
return (
<>
<div>
{data.map((faction) => (
<Text>
{faction.data.attributes.Title}
</Text>
))}
</div>
<Table variant="simple">
<TableCaption>Imperial to metric conversion factors</TableCaption>
<Thead>
<Tr>
<Th>To convert</Th>
<Th>into</Th>
<Th isNumeric>multiply by</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>inches</Td>
<Td>millimetres (mm)</Td>
<Td isNumeric>25.4</Td>
</Tr>
<Tr>
<Td>feet</Td>
<Td>centimetres (cm)</Td>
<Td isNumeric>30.48</Td>
</Tr>
<Tr>
<Td>yards</Td>
<Td>metres (m)</Td>
<Td isNumeric>0.91444</Td>
</Tr>
</Tbody>
<Tfoot>
<Tr>
<Th>To convert</Th>
<Th>into</Th>
<Th isNumeric>multiply by</Th>
</Tr>
</Tfoot>
</Table>
</>
);
};
API Response
{
"data": [
{
"id": 1,
"attributes": {
"Title": "Russia",
"faction_information": "# Russian Squad Faction\n",
"faction_lunch_date": "2015-02-28",
"createdAt": "2022-01-28T14:42:02.087Z",
"updatedAt": "2022-01-28T14:46:02.807Z",
"publishedAt": "2022-01-28T14:46:02.804Z",
"media_link": null
}
},
{
"id": 2,
"attributes": {
"Title": "US",
"faction_information": "# whatever",
"faction_lunch_date": null,
"createdAt": "2022-01-29T04:37:36.773Z",
"updatedAt": "2022-01-29T04:38:23.549Z",
"publishedAt": "2022-01-29T04:37:48.962Z",
"media_link": null
}
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 1,
"total": 2
}
}
}
You have an error when passing a key, here:
const { data, error} = useSWR(
["https://api-skr.herokuapp.com/api/factions", fetcher]
);
First argument of useSWR function is the key, second is the fetcher. You pass them as an array and useSWR thinks that this whole thing is a composite key (which is possible to have in advanced scenarios)
So just remove array brackets and pass key and fetcher as separate arguments:
const { data, error} = useSWR(
"https://api-skr.herokuapp.com/api/factions", fetcher
);

How to render my multidimensional array of objects in react

I am trying to render my array of objects inside my table but it shows "Cannot read property 'monthlytarget' of undefined", I am using axios to fetch the result and render inside the table
Axios :
http.get(apiReportsEndpoint+"?empid="+this.props.match.params.id)
.then(response =>{
this.setState({
report:response.data.data.monthlytarget
})
});
Response I receive from API
"data":{
"monthlytarget":[
{
"quarter":1,
"period_start":"2019-04-01",
"monthlytarget":{
"04":{
"targetpm":"213120",
"invoice_values":[
],
"revenuepm":0,
"targetpercentage":0,
"joinees":0
},
"05":{
"targetpm":"213120",
"invoice_values":[
],
"revenuepm":0,
"targetpercentage":0,
"joinees":0
}
}
},
{ quarter":2 ...},
{ quarter":3 ...},
]
}
I want to render values inside "monthlytarget" as rows inside table
<thead>
<tr>
<th>MONTH</th>
<th>TARGET PER MONTH</th>
<th>REVENUE PER MONTH</th>
</tr>
</thead>
<tbody>
{
this.state.report.map((revenuereport) =>{
{Object.keys.map.monthlytarget(premise,index) => (
<tr>
<td>{index}</td>
<td>{premise.targetpm}</td>
<td>{premise.revenuepm}</td>
</tr>
))}
})
}
</tbody>
To create one table out of all the data you could do the following:
this.state.report
.map(({ monthlytarget }) => Object.entries(monthlytarget))
.flat()
.map(([key,value], index) => (
<tr key={index}>
<td>{index}</td>
<td>{value.targetpm}</td>
<td>{value.revenuepm}</td>
</tr>
));
what do you mean by calling Object.keys.map.monthlytarget? if you are trying to loop the array and get JSX, do this:
this.state.report.map((revenuereport) =>
Object.keys(revenuereport.monthlytarget).map((premise, index) => (
<tr>
<td>{index}</td>
<td>{revenuereport.monthlytarget[premise].targetpm}</td>
<td>{revenuereport.monthlytarget[premise].revenuepm}</td>
</tr>
))
);
Do pay attention to indents and brackets, code snippet in the question seems not gonna work at all.
It should be...
this.state.report.map(({ monthlytarget }, i) =>
Object.values(monthlytarget).map({ targetpm, revenuepm }, i) =>
<tr>
<td>{i}</td>
<td>{targetpm}</td>
<td>{revenuepm}</td>
</tr>
))

Cannot render child object in React JS table

I am trying to render an object held in state. The contents of this.state is:
"statement": {
"creator": "8ff243b2-f21e-43f3-9090-4aa679fbeb1a",
"id": "bf4c965e-bd59-48c8-b31a-8f67529e5fde",
"impactors": [
"978388e8-2987-4c89-82b6-4da619d82935",
"1d75e2a7-bf2a-4f55-ba68-373752b24f98"
],
"score": {
"impactors": 2,
"net": 3,
"percentage": 0.75,
"total_votes": 4
},
"statement": "The Earth is round."
}
In my React JS app, I am able to render some of the child object (statement) but not all. Here is my code:
renderStatement() {
return (
<tbody>
<tr>
<td>Statement: </td>
<td>{this.state.statement.score}</td>
</tr>
</tbody>
)
}
As expected, the above code returns an error: Error: Objects are not valid as a React child (found: object with keys {impactors, net, percentage, total_votes}). If you meant to render a collection of children, use an array instead.
What I actually want is percentage, which is a node underneath score. When I try to drill down and have it just render percentage, like this:
renderStatement() {
return (
<tbody>
<tr>
<td>Statement: </td>
<td>{this.state.statement.score.percentage}</td>
</tr>
</tbody>
)
}
I get the following error: TypeError: Cannot read property 'percentage' of undefined.
Any of the other objects ('creator', 'id', 'impactors', 'statement') work just fine.
This is how I fixed it:
renderStatement() {
const score = this.state.statement.score || {}
return (
<tbody>
<tr>
<td>Statement: </td>
<td>{score.percentage}</td>
</tr>
</tbody>
)
}
you can do the following:
renderStatement() {
const text = (typeof this.state.statement.score !== "undifined") ?
this.state.statement.score.percentage : ''
return (
<tbody>
<tr>
<td>Statement: </td>
<td>{text}</td>
</tr>
</tbody>
)
}

localeCompare array of objects sorting not working [duplicate]

This question already has an answer here:
Why doesn't my arrow function return a value?
(1 answer)
Closed 3 years ago.
My issue similar to this, but not the same.
I'm trying to sort an array of objects using localeCompare. It has no reason not to work, but it's not working. Somehow I'm doing something right./sigh/
This is how I fetch the data
const [data, setData] = useState([]);
const [isBusy, setBusy] = useState(true)
useEffect(() => {
console.log('I was run once');
async function fetchData() {
const url = `${
process.env.REACT_APP_API_BASE
}/api/v1/endpoint/`;
axios.get(url).then((response: any) => {
setBusy(false);
setData(response.data.results)
console.log(response.data.results);
});
}
fetchData();
}, [])
This is the sorting function
function onSort(event: any, sortKey: string) {
const newData = data;
newData.sort((a: any, b: any): any => {
a[sortKey].toString().localeCompare(b[sortKey]);
})
setData(newData);
}
A typical array of objects is like this
[
{
"avatar": "big",
"name": "Username",
},
{
"avatar": "small",
"name": "Rex",
},
{
"avatar": "medium",
"name": "Duh",
}
]
Then the table header has this
<th scope="col" className="text-center" onClick={e => onSort(e, 'name')}>Name</th>
Although the onSort() function runs without any errors, the objects don't get sorted as per the sortKey
Am I using the localeCompare wrongly? What can I do to make the above right?
Also, the setData, I believe should update the data and thus trigger a rebuild of the table, which seems to be happening, because if I pass in an empty array to the setData, the table blanks.
edit:
The HTML part
...
return (
<div>
<table className="table table-hover">
<thead>
<tr>
<th onClick={e => onSort(e, 'avatar')}>Avatar</th>
<th onClick={e => onSort(e, 'name')}>Name</th>
</thead>
<tbody>
{data && data.map((item: any, index: any) => {
return (
<tr key={index}>
<th>{{ item.avatar}}</th>
<td>{item.name}</td>
<td className="text-center" style={{ 'fontSize': '32px', 'color': '#981c1e' }}>0</td>
</tr>
)
})}
</tbody>
</table>
</div>
...
If I understand, if the setData triggers a data change, the render re-renders?
You need a return statement by using curly brackets in an arrow function
newData.sort((a: any, b: any): any => {
return a[sortKey].toString().localeCompare(b[sortKey]);
});

Categories