React Hook useState Is Returning Undefined - reactjs

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()},[])

Related

How to Display data of single json file in mulitple tables through repeating a single react component

I am a beginner in ReactJS.
What I want is, I have a Component Datatable.js and I want to create three tables in that component by configuring data of a single JSON File and there should be only one component for three tables but the condition is the values in tables must come different-different in each table like- in first tables- Name, email, number; in the second table- email, city, number and in the third table- Name, Profession, number, city. I want to perform all that operation by repeating Datatable.js component three times in App.js so that three tables render, not by writing table element three times in Datatable.js.
So please tell me how to do that.
I have got the JSON values in the data state and I know it can be displayed through the map() method but the problem is how to send these JSON file values in each repeating component and how Datatable.js would get it so that values would appear differently in each table as I mentioned above?
data.json:
[
{
"person": {
"name": "Viswas Jha",
"avatar": "images/profile.jpg"
},
"city": "Mumbai",
"email": "vishwasjha#gmail.com",
"number": 123456,
"profession": "UI Designer"
},
{
"person": {
"name": "Damini Pandit",
"avatar": "images/profile.jpg"
},
"city": "Delhi",
"email": "daminipandit#gmail.com",
"number": 1345645,
"profession": "Front-end Developer"
},
{
"person": {
"name": "Nihal Lingesh",
"avatar": "images/profile.jpg"
},
"city": "Delhi",
"email": "nihallingesh#gmail.com",
"number": 12345689,
"profession": "UX Designer"
},
{
"person": {
"name": "Akash Singh",
"avatar": "images/profile.jpg"
},
"city": "Kolkata",
"email": "akashsingh#gmail.com",
"number": 1234566,
"profession": "Backend Developer"
}
]
App.js:
import Datatable from './Datatable';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import {useState, useEffect} from 'react';
import './App.css';
const fetchData = new Promise((myResolve, myReject) => {
let req = new XMLHttpRequest();
req.open('GET', "./data.json");
req.onload = function() {
if (req.status == 200) {
return myResolve(req.response);
} else {
return myReject("File not Found");
}
};
req.send();
});
function App() {
const [data, setData] = useState([]);
useEffect(() => {
fetchData.then((jsonData) => setData(JSON.parse(jsonData)));
}, []);
return (
<>
<Datatable Data = {data} />;
<Datatable Data= {data}/>;
<Datatable Data= {data}/>;
</>
);
}
export default App;
Datatable.js:
import React from 'react';
import Grid from '#material-ui/core/Grid';
export default function Datatable({Data}) {
return (
<div className='main text-center '>
<h1 className='head py-3'>Datatable</h1>
<Grid container spacing={1} className='contain m-auto mt-5 ps-5 pb-4'>
<table className="table table-striped">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Number</th>
</tr>
</thead>
<tbody>
{
Data.map((elem, ind)=>{
return (
<tr key={ind}>
<td className='d-flex justify-content-between align-items-center'>
<img src={elem.person.avatar} alt="avatar"/>
{elem.person.name}</td>
<td>{elem.email}</td>
<td>{elem.number}</td>
</tr>
)
})
}
</tbody>
</table>
</Grid>
</div>
);
}
Update your code with this.
App.js
import Datatable from './Datatable';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import { useState, useEffect } from 'react';
import './App.css';
const fetchData = new Promise((myResolve, myReject) => {
let req = new XMLHttpRequest();
req.open('GET', "./data.json");
req.onload = function () {
if (req.status == 200) {
return myResolve(req.response);
} else {
return myReject("File not Found");
}
};
req.send();
});
function App() {
const [sortReverse,setSortReverse] = useState(false)
const [data, setData] = useState({
tableOne: [],
tableTwo: [],
tableThree: [],
});
useEffect(() => {
fetchData.then(function (jsonData) {
const tableOne = jsonData?.map(
({ person: { name }, email, number }) => ({
name,
email,
number,
})
);
const tableTwo = jsonData?.map(({ city, email, number }) => ({
city,
email,
number,
}));
const tableThree = jsonData?.map(
({ person: { name }, email, number, city }) => ({
name,
email,
number,
city,
})
);
setData({
tableOne: tableOne,
tableTwo: tableTwo,
tableThree: tableThree,
});
});
}, []);
const handleSort = (e) => {
const sortedArrTableOne = data?.tableOne?.sort((a, b) => {
return sortReverse
? a[e.toLowerCase()]?.localeCompare(b[e.toLowerCase()])
: b[e.toLowerCase()]?.localeCompare(a[e.toLowerCase()]);
});
const sortedArrTableTwo = data?.tableTwo?.sort((a, b) => {
return sortReverse
? a[e.toLowerCase()]?.localeCompare(b[e.toLowerCase()])
: b[e.toLowerCase()]?.localeCompare(a[e.toLowerCase()]);
});
const sortedArrTableThree = data?.tableThree?.sort((a, b) => {
return sortReverse
? a[e.toLowerCase()]?.localeCompare(b[e.toLowerCase()])
: b[e.toLowerCase()]?.localeCompare(a[e.toLowerCase()]);
});
setData((prevState) => ({
...prevState,
tableOne: sortedArrTableOne,
tableTwo: sortedArrTableTwo,
tableThree: sortedArrTableThree,
}));
setSortReverse(!sortReverse)}
const tableOneColumns = [
{ name: "Name", accessor: "name" },
{ name: "Email", accessor: "email" },
{ name: "Number", accessor: "number" },
];
const tableTwoColumns = [
{ name: "Email", accessor: "email" },
{ name: "City", accessor: "city" },
{ name: "Number", accessor: "number" },
];
const tableThreeColumns = [
{ name: "Name", accessor: "name" },
{ name: "Email", accessor: "email" },
{ name: "Number", accessor: "number" },
{ name: "City", accessor: "city" },
];
return (
<>
<Datatable data={data?.tableOne} columns={tableOneColumns} handleSort={handleSort}/>
<Datatable data={data?.tableTwo} columns={tableTwoColumns} handleSort={handleSort}/>
<Datatable data={data?.tableThree} columns={tableThreeColumns} handleSort={handleSort}/>
</>
);
}
export default App;
DataTable.js
import React from "react";
import Grid from "#material-ui/core/Grid";
import { Box } from "#material-ui/core";
export default function Datatable(props) {
const { data = [], columns = [], handleSort } = props;
return (
<Box className="main text-center ">
<h1 className="head py-3">Datatable</h1>
<Grid container spacing={1} className="contain m-auto mt-5 ps-5 pb-4">
<table className="table table-striped ">
<thead>
<tr>
{columns?.map((column, index) => (
<th key={index} scope="col" onClick={()=> handleSort(column?.name)}>
{column?.name}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((elem, ind) => {
return (
<tr key={ind}>
{columns?.map((column) => {
return (
<td className="d-flex justify-content-between align-items-center">
{elem?.[column.accessor]}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</Grid>
</Box>
);
}
Note: If you want to sort then click on heading Name or Email etc.

How to get an object inside another object using with useState - Next.js

I have this Object on get:
[
{
"id": {
"M49": 20,
"ISO-3166-1-ALPHA-2": "AD",
"ISO-3166-1-ALPHA-3": "AND"
},
"nome": {
"abreviado": "Andorra",
"abreviado-EN": "Andorra",
"abreviado-ES": "Andorra"
},
"area": {
"total": "468",
"unidade": {
"nome": "quilômetros quadrados",
"símbolo": "km2",
"multiplicador": 1
}
},
"localizacao": {
"regiao": {
"id": {
"M49": 150
},
"nome": "Europa"
},
"sub-regiao": {
"id": {
"M49": 39
},
"nome": "Europa meridional (Sul da Europa)"
},
"regiao-intermediaria": null
},
"linguas": [
{
"id": {
"ISO-639-1": "ca",
"ISO-639-2": "cat"
},
"nome": "catalão"
}
],
"governo": {
"capital": {
"nome": "Andorra-a-Velha"
}
},
"unidades-monetarias": [
{
"id": {
"ISO-4217-ALPHA": "EUR",
"ISO-4217-NUMERICO": "978"
},
"nome": "Euro"
}
],
"historico": "O Principado de Andorra é um dos menores Estados da Europa, situado no alto dos Pireneus, entre as... "
}
]
I can't return every "nome": {"abreviado":"Andorra"}
import styles from "../styles/Home.module.css";
import { useEffect, useState } from "react";
let url = "https://servicodados.ibge.gov.br/api/v1/paises";
export default function Home() {
let [countryfact, setCountryfact] = useState([null]);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((result) => setCountryfact(result));
}, []);
console.log(countryfact)
return (
<div style={{ color: "blue" }}>
<ul>
{countryfact.map((country, name) => (
<li key={country.name}>
<span>name: {countryfact.name}</span> <span>age: {countryfact.id}</span>
</li>
))}
</ul>
</div>
);
}
I want to return the object inside another object but i can't do it with my code
My return on screen, is empty, but there a lot empty lines returning.
Maybe this is a simple, but i try with another ways without results
Return on screen
There is no property called name in your json data. Also I don't see any reason you print age as {countryfact.id} which by the way is an object.
<li key={country.name}>
<span>name: {countryfact.name}</span> <span>age: {countryfact.id}</span>
</li>
You can try like this:
<div style={{ color: "blue" }}>
<ul>
{countryfact.map((country) => (
<li key={country.nome.abreviado}>
<span>name:{country.nome.abreviado}</span>
{" "}
<span>area:{country.area.total}</span>
</li>
))}
</ul>
</div>
The problem is that you are passing the wrong variable inside tag li.
you called<span>name: {countryfact.name} observe that you used "countryfact"
the correctly is like <span>name: {country.name}.

.map is not a function with MaterialUI Tab component

So I'm trying to populate a in-memory MirageJS' database with a post request of the data in useEffect, making a get request after. Then I save the result to columnsNames state and use it to map this array and read the information as a label of Tab MaterialUI component. However, I have some questions:
When I put a console.log after useEffect or inside get request, both response.data and columnsNames state have 2 identical arrays. Why 2 and not 1?
At runtime the console prints
Uncaught TypeError: columnsNames.map is not a function
The above error occurred in the <ScrollableTabsButtonAuto> component
and nothing is showed in the screen. Why?
Anyone that could help me, thanks in advance!
Data.ts
export const data = [
{
"id": 1,
"title": "Test 1",
"steps": [
"Read a starting book",
"Keep calm and drink coffee",
"Don't Panic so much",
]
},
{
"id": 2,
"title": "Test 2",
"steps": [
"Get some helpful help",
"Don't loose yourself",
]
},
{
"id": 3,
"title": "Test 3",
"steps": [
]
},
{
"id": 4,
"title": "Test 4",
"steps": [
]
},
{
"id": 5,
"title": "Test 5",
"steps": [
]
},
{
"id": 6,
"title": "Test 6",
"steps": [
]
}
]
App.tsx
import { createServer, Model } from 'miragejs';
createServer({
models: {
columns: Model,
},
routes() {
this.namespace = 'api'
this.get('columns', () => {
return this.schema.all('columns')
})
this.post('columns', (schema, request) => {
const data = JSON.parse(request.requestBody)
return schema.create('columns', data)
})
}
})
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Tab.tsx
interface ColumnTypes {
id: number,
title: string,
steps: string[]
}
export default function ScrollableTabsButtonAuto() {
const [value, setValue] = React.useState(0);
const [columnsNames, setColumnsNames] = React.useState<ColumnTypes[]>([])
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
React.useEffect(() => {
api.post('columns', data)
.then( () => {
api.get<ColumnTypes[]>('columns')
.then(response => setColumnsNames(response.data))
})
}, [])
return (
<Box sx={{ maxWidth: { xs: 320, sm: 1280 }, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
variant="scrollable"
scrollButtons="auto"
aria-label="scrollable auto tabs example"
>
{columnsNames.map(({ id, title }) => (
<Tab
label={title}
key={id}
/>)
)}
</Tabs>
</Box>
);
}

React JS, display Data from Nested json

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>

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