Populate table with JSON data and variable columns - reactjs

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>

Related

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

Get data id in array object vuejs

how to display value data based on index array. here I make a modal edit data, I have a json like the following
[
{
"ID": 3,
"idusers": 3,
"skills": "Go",
"profiency": "Expert",
},
{
"ID": 48,
"skills": "laravel",
"profiency": "laravel",
},
{
"ID": 47,
"skills": "Vue",
"profiency": "Expert",
}
]
table data that I display
<tr v-for="(skill, index) in getSkills" :key="skill.ID">
<td>{{ index + 1 }}</td>
<td>{{ skill.skills }}</td>
<td>{{ skill.profiency }}</td>
<td class="text-xs-center">
<td><div v-if="editBtn == true">
<v-btn class="mr-3" x-small fab v-on:click="handleEdit(item,index)" color="primary"><v-icon>mdi-cancel</v-icon></v-btn>
</td>
</tr>
and this my modal edit
<v-dialog v-model="skillForm" width="500">
<v-container>
<v-text-field outlined dense>
{{profiency}}
</v-text-field>
</v-container>
</v-dialog>
my method
export default {
name: "Skills",
data: function() {
return {
formAddSkills: "",
skillForm: false,
editBtn: "",
skills: {
skills: "",
profiency: "",
},
};
},
computed: {
...mapState({ getSkills: (state) => state.resume.Skills }),
dataSkills() {
return this.skills;
},
},
methods: {
handleEdit(item, index) {
this.skillForm = true;
this.editBtn = true;
this.profiency = item.profiency
// console.log(item)
console.log(index)
},
}
}
the question is how to display data based on ID, when I click the edit button it appears and enters the text field form
Pass skill from your method as a parameter
#click="handleEdit(skill,index)
Then declare a variable, currentObject, and then equate it this.currentObject = skill inside the method.
Then you can access currentObject.id via the v-model binded to your text field.

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>

How to sort data in ReactJs

I have Items data which I am attempting to display array values sorted by cost field in costtable array when roomname is Double and type is 2.Here is my code:
Json:
{
"index": 1,
"id": "5e3961face022d16a03b1de9_1023632_1004876",
"costtable": [
{
"roomname": "Single",
"room_id": "1023632_479490,1004876_385485",
"family": [
{
"title": "adult 1",
"cost": 3.7568000,
"unit": "10",
"type": "2"
}
]
}
]
},
{
"index": 2,
"id": "5e3961face022d16a03b1de9_1088496_1005362",
"costtable": [
{
"roomname": "Double",
"room_id": "1088496_447339,1005362_415279",
"family": [
{
"title": "adult 1",
"cost": 5.6868000,
"unit": "10",
"type": "2"
}
]
}
]
},
{
"index": 3,
"id": "5e3961face022d16a03b1de9_1141859_1005529",
"costtable": [
{
"roomname": "Single",
"room_id": "1141859_74888,1005529_870689",
"family": [
{
"title": "adult 1",
"cost": 5.9586000,
"unit": "10",
"type": "2"
}
]
}
]
}
]
Code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
Items: [],
library: null,
perPage: 20,
currentPage: 1,
maxPage: null,
}
}
componentDidMount() {
fetch('/json', {
method: 'GET',
})
.then(response => response.text())
.then(text => {
let Maindata = JSON.parse(text.replace(/\'/g, '"'))
let CostSort = Maindata.map(a => {
return this.renderSort(a)
})
Maindata.sort((a, b) => a.CostSort - b.CostSort);
this.setState(state => ({
...state,
Items: Maindata
}), () => {
this.reorganiseLibrary()
})
}).catch(error => console.error(error))
}
reorganiseLibrary = () => {
const { perPage, Items } = this.state;
let library = Items;
library = _.chunk(library, perPage);
this.setState({
library,
currentPage: 1,
maxPage: library.length === 0 ? 1 : library.length
});
};
renderSort(element) {
let indents = []
let lenFamilies = element.costtable.length
for (let i = 0; i < lenFamilies; i++) {
if (element.costtable[i].roomname.indexOf('Double') > -1) {
for (let j = 0; j < element.costtable[i].family.length; j++) {
if (element.costtable[i].family[j].type == 2) {
indents.push(element.costtable[i].family[j].cost)
break;
}
}
break;
}
}
return (indents)
}
// Previous Page
previousPage = event => {
this.setState({
currentPage: this.state.currentPage - 1
});
};
// Next Page
nextPage = event => {
this.setState({
currentPage: this.state.currentPage + 1
});
};
// handle per page
handlePerPage = (evt) =>
this.setState({
perPage: evt.target.value
}, () => this.reorganiseLibrary());
// handle render of library
renderLibrary = () => {
const { library, currentPage } = this.state;
if (!library || (library && library.length === 0)) {
return '';
}
return library[currentPage - 1].map((item, i) => (
<div className="item-list">
{item.index}
</div>
));
};
render() {
const { library, currentPage, perPage, maxPage } = this.state;
return (
<div>
<div className="wrapper-data">
{this.renderLibrary()}
</div>
<div class="clr"></div>
<ul id="page-numbers">
<li className="nexprevPage">
{currentPage !== 1 && (
<button onClick={this.previousPage}><span className="fa-backward"></span></button>
)}
</li>
<li className="controlsPage active">{this.state.currentPage}</li>
<li className="restControls">...</li>
<li className="controlsPage">{this.state.maxPage}</li>
<li className="nexprevPage">
{(currentPage < maxPage) && (<button onClick={this.nextPage}><span className="fa-forward"></span></button>
)}
</li>
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('Content'));
This code does not give me any errors but displays the values in an unsorted format. How can I sort it?
New Code
Maindata.sort((a, b) => {
let lenFamilies = a.costtable.length
for (let i = 0; i < lenFamilies; i++) {
if( a.costtable[i].roomname.indexOf('Double') > -1){
for (let j = 0; j < a.costtable[i].family.length; j++) {
if( a.costtable[i].family[j].type == 2){
a.costtable[i].family[j].cost- b.costtable[i].family[j].cost
}
}
}
}
}
I do not understand the exact formula that you are using to sort, but what you are doing before the sort is wrong.
In your componentDidMount
let CostSort = Maindata.map(a => { return this.renderSort(a) })
This returns an array into a variable called CostSort and does not affect MainData in any way.
However, later on you do this.
Maindata.sort((a, b) => a.CostSort - b.CostSort);
For first iteration, this will compare Maindata[0] and Maindata[1]. Note that there is no CostSort in either of the objects and hence you are performing operation of undefined - undefined which is NaN. Therefore no sorting happens.
I would suggest you use only the sort function and do your comparison between two values there.
Maindata.sort((a, b) => {
// Do your calculation here
if(a should be before b) {
return -1;
} else {
return 1;
}
}
P.S The convention for variable in js is camelCase and not PascalCase. So, Maindata should he mainData.
EDIT:
Here is a simple sort implementation which works for the above case, you can expand on it according to your full use case.
Maindata.sort((a, b) => {
let lenFamilies = a.costtable.length;
for (let i = 0; i < lenFamilies; i++) {
if (
a.costtable[i].roomname.includes("Double") &&
!b.costtable[i].roomname.includes("Double")
) {
return -1;
}
if (
!a.costtable[i].roomname.includes("Double") &&
b.costtable[i].roomname.includes("Double")
) {
return 1;
}
if (a.costtable[i].roomname.indexOf("Double") > -1) {
for (let j = 0; j < a.costtable[i].family.length; j++) {
if (a.costtable[i].family[j].type == 2) {
a.costtable[i].family[j].cost - b.costtable[i].family[j].cost;
}
}
}
}
});
Omitting the algorithms (bubble, quicksort, by inserting ...). There is possible of sorting in UI context.
Your json have:
title | cost | unit | type
What type of sort You need? (title is string (can eg. sort alphabetically), then cost, unit & type are number (ascending + descending)
It's will be helpfull - when in future You provide only neccessary piece of code.
Here it's ellegant minimalistic function responsible for asc/desc sorting.
Firstly it's need to pass the props(which You wan't to sort) to values state.
function App() {
const [ascValue, setAscValue] = useState(true);
const [values, setValues] = useState([10, 5, 12, 1, 2, 900, 602]);
function sortValues() {
const compare = ascValue ? (a, b) => a - b : (a, b) => b - a;
setValues([...values].sort(compare));
}
useEffect(() => {
sortValues();
}, [ascValue]);
return (
<div>
<h3>{ascValue.toString()}</h3>
<button onClick={() => setAscValue(!ascValue)}>Toggle Asc</button>
{values.map(v => (
<p key={v}>{v}</p>
))}
</div>
);
}
Here is sorting by cost for your object:
let text = [{
"index": 1,
"id": "5e3961face022d16a03b1de9_1023632_1004876",
"costtable": [
{
"roomname": "Single",
"room_id": "1023632_479490,1004876_385485",
"family": [
{
"title": "adult 1",
"cost": 3.7568000,
"unit": "10",
"type": "2"
}
]
}
]
},
{
"index": 2,
"id": "5e3961face022d16a03b1de9_1088496_1005362",
"costtable": [
{
"roomname": "Double",
"room_id": "1088496_447339,1005362_415279",
"family": [
{
"title": "adult 1",
"cost": 5.6868000,
"unit": "10",
"type": "2"
}
]
}
]
},
{
"index": 3,
"id": "5e3961face022d16a03b1de9_1141859_1005529",
"costtable": [
{
"roomname": "Single",
"room_id": "1141859_74888,1005529_870689",
"family": [
{
"title": "adult 1",
"cost": 5.9586000,
"unit": "10",
"type": "2"
}
]
}
]
}
]
const App = () =>{
const usersWithName = Object.keys(text).map(function(key) {
var user = text[key];
return user.costtable[0].family[0].cost;
});
let costArray = usersWithName
const [ascValue, setAscValue] = useState(true);
const [values, setValues] = useState(costArray);
function sortValues() {
const compare = ascValue ? (a, b) => a - b : (a, b) => b - a;
setValues([...values].sort(compare));
}
useEffect(() => {
sortValues();
}, [ascValue]);
return (
<div>
<h3>{ascValue.toString()}</h3>
<button onClick={() => setAscValue(!ascValue)}>Toggle Asc</button>
{values.map(v => (
<p key={v}>{v}</p>
))}
</div>
);
}
export default App;
I don't have idea of performance in this case + if in your json are more costtable & family it should iterate by [i] iterator.

Angular JS Order By Filter not working for dynamic predicates

I am creating a Grid Control in Angular JS. ( I don't want to use ng-grid, smart table, etc for some reason)
Plunkr URL : http://plnkr.co/edit/arkCZcfXTIsW7sFCxisn?p=preview
On top of the table generated, i have populated fields in the combobox so that user is allowed to search on specific columns
or as free search.
As I see From here :
https://docs.angularjs.org/api/ng/filter/filter
For free search, I have used the syntax as {$:Value} and for column based search, {ColName:Value} syntax
However, I am unable to get it to work when I bind the column names to combobox.
I do get static search to work Eg if I write {'Code':"1"}, this works. but if I take "Code" from a combobox, it doesnt work.
Need help on setting dynamic filter.
This one also does not seem to help.
angular filter with dynamic list of attributes to search
This is the HTML
<div ng-controller="MyGrid">
Search in Column
<select ng-model="FilterByColumn" >
<option value="$">All</option>
<option ng-repeat="hdr in headers | orderBy : hdr.displayOrder" ng-show="hdr.isVisible" value="{{hdr.name}}" >
{{hdr.caption}}
</option>
</select>
Value : <input type="text" ng-model="searchKeyword" />
<br />
Remove Sort
<table>
<thead>
<tr>
<th ng-repeat="hdr in headers | orderBy : hdr.displayOrder" ng-show="hdr.isVisible">
{{hdr.caption}}
</th>
</tr>
</thead>
<tbody>
<%--<tr ng-repeat="dataVal in data | filter: {FilterByColumn : searchKeyword} | orderBy:predicate:reverse "> **Does not work--%>**
<%--<tr ng-repeat="dataVal in data | filter: {$ : searchKeyword} | orderBy:predicate:reverse "> **This works--%>**
<tr ng-repeat="dataVal in data | filter: GetFilter (FilterByColumn, searchKeyword) | orderBy:predicate:reverse "> **<!-- Does not work -->**
<td ng-repeat="hdr in headers | orderBy : hdr.displayOrder" ng-show="hdr.isVisible">
{{dataVal[hdr.name]}}
</td>
</tr>
</tbody>
</table>
<pre>Sorting predicate = {{predicate}}; reverse = {{reverse}} ; SearchBy = {{FilterByColumn}} ; Search Key : {{searchKeyword}} </pre>
</div>
This is the JS :
'use strict';
var MyApp = angular.module('MyApp', []);
MyApp.controller('MyGrid', function ($scope) {
$scope.predicate = 'Code';
$scope.reverse = false;
$scope.FilterByColumn = '$';
$scope.headers = [
{
"name": "Code",
"caption": "Code",
"isVisible": true,
"displayOrder": 12
},
{
"name": "DispName",
"caption": "My Name",
"isVisible": true,
"displayOrder": 1
},
{
"name": "Locked",
"caption": "Islocked",
"isVisible": true,
"displayOrder": 2
}
];
$scope.data =
[{
"Code": "1",
"DispName": "abdul",
"Locked": "0"
},
{
"Code": "2",
"DispName": "Hemant",
"Locked": "0"
},
{
"Code": "3",
"DispName": "Rahul",
"Locked": "0"
},
{
"Code": "4",
"DispName": "Sandy",
"Locked": "0"
},
{
"Code": "5 ",
"DispName": "Nihal",
"Locked": "0"
},
{
"Code": "6",
"DispName": "Sachin",
"Locked": "0"
},
{
"Code": "7",
"DispName": "abdul f",
"Locked": "0"
},
{
"Code": "8",
"DispName": "abdul g",
"Locked": "0"
},
{
"Code": "9",
"DispName": "abdul h ",
"Locked": "0"
},
{
"Code": "10",
"DispName": "abdul i",
"Locked": "0"
}
];
$scope.getValue = function (obj, PropName) {
return obj[PropName];
};
$scope.SetSort = function (objName) {
//alert(objName);
$scope.predicate = objName;
$scope.reverse = !$scope.reverse;
};
$scope.GetFilter = function (srchCol, Srchval) {
//alert(srchCol);
//alert(Srchval);
if (srchCol == "") {
return { $: Srchval };
}
else {
return { srchCol: Srchval };
}
};
});
That is because when you write { srchCol: Srchval } - srcCol is the property name and is not substituted with the value in variable srcCol, try this syntax instead:
$scope.GetFilter = function (srchCol, Srchval) {
if (srchCol == "") {
return { $: Srchval };
}
else {
filter = {};
filter[srchCol] = Srchval;
return filter;
}
};

Resources