localeCompare array of objects sorting not working [duplicate] - javascript

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]);
});

Related

Component not displaying updated data when field is updated?

I have a table that is populated with data from an array of objects.
This data is collected via websockets. The socket listens to 3 different events, and does one of the following to the array based on the event:
User can add an entry - ✅ works fully
User can modify an entry - issue
User can delete an entry - ✅ works fully
Here is the code:
interface TableEntry {
address: string;
amount: string;
children?: React.ReactNode;
}
export const TableComponent: FC = () => {
const socket = useContext(SocketContext);
const [entriesArray, setEntriesArray] = useState<TableEntry[]>([]);
const { globalAddress } =
useContext(UserDetailsContext);
useEffect(() => {
socket.on("end", (data) => {
setEntriesArray([]);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
var tempEntries = entriesArray;
socket.on("entry", (data) => {
tempEntries.push({ address: data[0], amount: data[1] });
setEntriesArray(tempEntries);
tempEntries = [];
});
return function () {
socket.off("entry");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Update an entry on request from a user
useEffect(() => {
var tempEntries = entriesArray;
socket.on("updateEntry", (data) => {
const findFunc = (element: TableEntry) => element.address == data[0];
const index = tempEntries.findIndex(findFunc);
tempEntries[index].address = "updated address";
setEntriesArray(tempEntries);
tempEntries = [];
});
return function () {
socket.off("updateEntry");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<Box>
<Box
w="427px"
h="450px"
top={"72px"}
right={"60px"}
bg={"rgba(255, 255, 255, 0.08)"}
position={"absolute"}
borderRadius="21"
overflow="hidden"
>
<TableContainer>
<Table variant="simple">
<Thead>
<Tr>
<Th fontFamily={"Orbitron"}>Address</Th>
<Th fontFamily={"Orbitron"}>Amount</Th>
</Tr>
</Thead>
<Tbody>
{entriesArray?.map((entry) => {
return entry.address == globalAddress ? (
<Tr key={entry.address} bg={"rgba(255, 255, 255, 0.05)"}>
<Td fontFamily={"Orbitron"}>{shorten(entry.address)}</Td>
<Td fontFamily={"Orbitron"}>{entry.amount}</Td>
</Tr>
) : (
<Tr key={entry.address}>
<Td fontFamily={"Orbitron"}>{shorten(entry.address)}</Td>
<Td fontFamily={"Orbitron"}>{entry.amount}</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
</Box>
</Box>
</>
);
};
The data updates, but it is not being displayed in the table - how can I make sure the data in the table reflects the update?
I assumed since I am changing the state of entriesArray that forced a rerender - but that doesn't seem to be the case?
React's state is immutable, so you cannot update an array item directly. You need to use map with {...element} for cloning and updating that array item at the same time.
useEffect(() => {
socket.on("updateEntry", (data) => {
const tempEntries = entriesArray.map((element: TableEntry) =>
element.address == data[0]
? { ...element, address: "updated address" } //update the found element
: element //keep the original element
);
setEntriesArray(tempEntries);
tempEntries = [];
});
return function () {
socket.off("updateEntry");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Note that, you also can clone the entire array with a spread operator like [...entriesArray] or cloneDeep before updating any element, but it's not preferable because it means you want to render the entire array (even though you want to render the updated element only) which hits performance if you have a huge array.

render custom component on table based on the value of my data object?

I have a Table component which I want to render custom component based on my data (which is an array of objects) also I need my component (I mean Table component) to have two props, one is the data object and another one is an array of objects which each object has two properties: a title and a function that render different component based on the data key. for example if the key in data object is fullName, I need to render a paragraph tag or if key is avatar, I need to return an image tag and so on.. let me show in code:
const Table = () => {
const data= [
{
id: 1,
avatar: 'blah blah blah',
fullName: 'Arlan Pond',
email: 'apond0#nytimes.com',
country: 'Brazil',
registerDate: '1/11/2021',
status: 'active',
},
];
const cols = [
{
title: 'ID',
componentToRender(rowsArr) { //this is how I defined my method.
rowsArr.map((el, index) => {
return <td>{el.id}</td>;
});
},
},
{
title: 'Avatar',
componentToRender(rowsArr) { //this is how I defined my method.
rowsArr.map((el, index) => {
return <td><span>{el.avatar}</span></td>;
});
},
},
];
return (
<div className='table-responsive' style={{width: '95%'}}>
<table className='table table-borderless'>
<thead>
<tr>
//here I need to show my headers...
{cols.map((el, index) => {
return <th key={index}>{el.title}</th>;
})}
</tr>
</thead>
<tbody>
//here I need to fill my table with components.
<tr className='table-row'>
{cols.map((el, index) => {
return el.componentToRender(data);
})}
</tr>
</tbody>
</table>
</div>
);
};
and the problem is table show only headers but data cells are empty. how can I achieve this?
This is one of the objects that you have defined.
{
title: 'ID',
componentToRender(rowsArr) { //this is how I defined my method.
rowsArr.map((el, index) => {
return <td>{el.id}</td>;
});
},
}
if you look closely , you will see that you are not returning anything from the function. The return keyword that you have used, goes back to the map method. So to fix the problem, you will need to change the code like this:
{
title: 'ID',
componentToRender(rowsArr) { //this is how I defined my method.
return rowsArr.map((el, index) => {
return el.id;
});
},
}
and tbody logic needs to change as well:
<tbody>
<tr className='table-row'>
{cols.map((el, index) => {
return <td key={index}>{el.componentToRender(rows)}</td>;
})}
</tr>
</tbody>
I refactored the component rendering. Instead of looping trough data in every component render, I created a data.map inside <tbody>, which creates a <tr> for every data entry (every object inside data array) and then renders the needed component based on the title.
Beware! title in cols should have the same naming case as the one in data. (for example both should be Avatar or avatar)
import "./styles.css";
const Table = () => {
const data= [
{
ID: 1,
Avatar: 'blah blah blah', // Capitalized like the Avatar in cols
fullName: 'Arlan Pond',
email: 'apond0#nytimes.com',
country: 'Brazil',
registerDate: '1/11/2021',
status: 'active',
},
{
ID: 2,
Avatar: 'blah blah blah2',
fullName: 'Arlan Pond',
email: 'apond0#nytimes.com',
country: 'Brazil',
registerDate: '1/11/2021',
status: 'active',
},
];
const cols = [
// refactored the components
{
title: 'ID',
component(content) { return <td>{content}</td> }
},
{
title: 'Avatar',
component(content) { return <td><span>{content}</span></td> }
},
];
return (
<div className='table-responsive' style={{width: '95%'}}>
<table className='table table-borderless'>
<thead>
<tr>
{cols.map((el, index) => {
return <th key={index}>{el.title}</th>;
})}
</tr>
</thead>
<tbody>
{/* completely changed here */}
{data.map((entry, entryindex) => {
return <tr className='table-row'>
{cols.map((el, index) => {
return el.component(data[entryindex][el.title])
})}
</tr>
})}
</tbody>
</table>
</div>
);
};
export default function App() {
return (
<div className="App">
<Table />
</div>
);
}
See it live in Codesandbox

How can I destructure a JSON response into a table?

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>

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>
))

How to sort the data in asc and desc order in table

I am new to react js. Here, I am trying to sort the data when the user clicks on the icons.
<th scope="col">Technology <i className="fa fa-fw fa-sort sort-icon"></i></th>
So, Now I have data which is in the form of an array of objects.
In this, I have 5 columns and sort icon is on every column. So, How do I Implement this thing using the react?
I want to sort using the alphabetical order.
My data looks like,
[{
"id": "5b7d4a566c5fd00507501051",
"hrmsJdId": null,
"companyId": null,
"jdName": "Senior/ Lead UI Developer",
"jobDescription": null,
"technology": java,
}, {
"id": "5b7fb04d6c5fd004efdb826f",
"hrmsJdId": null,
"companyId": null,
"jdName": "Content Innovation Lead",
"jobDescription": null,
"technology": css
}, {
"id": "5b7fb0b26c5fd004efdb8271",
"hrmsJdId": null,
"companyId": null,
"jdName": "Urgent Opening for DSP Engineer/ Senior Engineer for",
"jobDescription": null,
"technology": react,
}]
<td>{item.technology}</td>
<td>17</td>
<td title={item.jdName} className="jd-name-container justify-content-center align-items-center">
<div className="jdName">{item.jdName}</div>
{(key + 1 === 1) && <div className="badge new-badge badge-warning-custom">New</div>}
</td>
This is how I render the data. Now,
By default, I sort it using the
<tbody className="text-center">
{props.jobList && props.jobList && props.jobList.length > 0 && props.jobList.sort((a, b) => b.createdAt - a.createdAt).map((item, key) => {
return (
<tr key={key}>
<td align="center"> <input type="checkbox" name="myTextEditBox" value="checked" /></td>
<td>{item.technology}</td>
<td>17</td>
<td title={item.jdName} className="jd-name-container justify-content-center align-items-center">
<div className="jdName">{item.jdName}</div>
{(key + 1 === 1) && <div className="badge new-badge badge-warning-custom">New</div>}
</td>
<td>30</td>
<td>30</td>
<td>
</tbody>
So, How do I implement this ?
What I did is ,
<th scope="col">Technology<i className="fa fa-fw fa-sort sort-icon" onClick={props.sortAscending('Technology')}></i></th>
then in container
sortData = (key,event) => {
console.log("key is,", key);
this.props.sortAscending(key);
}
<UserJobsTabel jobList={filteredList} sortAscending={this.sortData} />
passed as a props.
Now in action,
export const sortAscending = (type) => {
return {
type: "SORT_ASCENDING",
payload: type
}
}
In reducer,
case FETCHING_JOBDESCRIPTION_SUCCESS:
return {
...state,
jobList: action.data.jobData ? action.data.jobData.sort((a, b) => b.createdAt - a.createdAt) : action.data.jobData,
yesterDayScore: action.data.yesterdayScore,
todayScore: action.data.todayScore,
error: false,
}
case "SORT_ASCENDING":
const { sortKey } = action.payload;
const jobList = [ ...state.jobList ]
.sort((a, b) => a[sortKey].localeCompare(b[sortKey]));
return { ...state, jobList };
×
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I am getting this error.
Use localeCompare() to sort in alphabetical order.
Have the sorting done in reducer while the component just dispatches the "sort" action. Every re-sort will cause a component re-render for jobList prop (in mapStateToProps) is updated.
In reducer:
const initialState = {
jobList: [],
};
export const jobList = (state = initialState, action) => {
switch(action.type) {
case Action.SORT_ALPHA_ASC:
const { sortKey } = action.payload;
const jobList = [ ...state.jobList ]
.sort((a, b) => a[sortKey].localeCompare(b[sortKey]));
return { ...state, jobList };
default:
return state;
}
}
In your component:
// alphaSort will dispatch action: SORT_ALPHA_ASC
const { jobList, alphaSort } = this.props;
if (! jobList || jobList.length < 1) {
// no job list
return null;
}
return (
<table>
<thead>
{ /* TODO: use th/td */ }
<SomeIcon name='asc-technology' onClick={() => alphaSort('technology')} />
<SomeIcon name='asc-jdname' onClick={() => alphaSort('jdName')} />
{ /* TODO: other fields */ }
</thead>
<tbody>
{
jobList.map((job) => ({
<tr key={job.id}>
<td name="technology">{job.technology}</td>
<td title={job.jdName}>
<div className="jdName">{job.jdName}</div>
</td>
{ /* TODO: other fields */ }
</tr>
}))
}
</tbody>
</table>
);
NOTE:
descending alpha sort and other fields will be for the OP to continue. :)
React Js arrays have a sort function:
['b', 'a'].sort((e1, e2) => e1.id < e2.id ? 1 : - 1)
If the lambda function returns 0, it does nothing. Otherwise, the two elements in the array will be sorted by the sign of the returned value.
If there are duplicate IDs in the database then the function will return 0.
To swap the order, change either the sign of the return value or the "smaller than" operator to a greater than operator.

Categories