I have this method for sorting an array of objects, but when I have a complex structure, for example:
const data = [ { title: 'Book title', author: 'John', info: { language: english, pages: 500, price: '$50' }} ]
I can't sort the second level of the object 'info: {language: english, pages: 500, price:' $ 50 '}'
My Code:
import { useMemo, useState } from 'react';
interface SortConfigProps {
key: string;
direction: string;
}
export const useSortableData = <T>(items: T[]) => {
const [sortConfig, setSortConfig] = useState<SortConfigProps>(
{} as SortConfigProps,
);
const sortedItems = useMemo(() => {
const sortableItems = [...items];
if (sortConfig) {
sortableItems.sort((a: any, b: any) => {
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === 'ascending' ? -1 : 1;
}
if (a[sortConfig.key] > b[sortConfig.key]) {
return sortConfig.direction === 'ascending' ? 1 : -1;
}
return 0;
});
}
return sortableItems;
}, [items, sortConfig]);
const requestSort = (key: string) => {
let direction = 'ascending';
if (
sortConfig &&
sortConfig.key === key &&
sortConfig.direction === 'ascending'
) {
direction = 'descending';
}
setSortConfig({ key, direction });
};
return { items: sortedItems, requestSort, sortConfig };
};
The problem you are running into is deep cloning of an object. the spread operator only goes 1 level deep and you are trying to go two. You can use libraries from lodash or other deep cloning. I use the JSON trick.
const [data, setData] = useState(initData);
function newArray() {
return JSON.parse(JSON.stringify(data));
}
Example shows sorting with two levels: https://codesandbox.io/s/serverless-sea-jt220?file=/src/App.js
There are multiple ways to implement this based on your overall system so for my suggestion, I am going to assume the following:
'info.price'
, or an arrow in 'info->price'
). An important property of the delimiter is that it is not valid to use it in a flat key (ie in the last example something like data = [{ 'info->price': '$50' }]
is not allowed)Ok now you just need to implement an accessor to use the complex keys on your object, something similar to Lodash.get . A simple implementation would be something like:
const DELIMITER = '->';
function get(obj, key) {
if (!obj) {
// in case of null or undefined
return obj;
}
if (!key) {
// empty string or something like that
return obj;
}
if (key.includes(DELIMITER)) {
const keyComponents = key.split(DELIMITER);
const firstKeyComponent = keyComponents.shift();
const newKey = keyComponents.join(DELIMITER);
return get(obj[firstKeyComponent], newKey)
}
return obj[key];
}
Emphasis on the simple here, because recalculating keyComponents
every time is not ideal. You also might want add extra if cases for how to handle strings or arrays, those could cause problems if a key component is a number.
EDIT: Also maybe use Object.prototype.hasOwnProperty
to check if a key is identical to a built in Object function, or better yet check if obj is a function and decide how to handle that scenario.
After you have this you can just replace that compare segment of your code with this:
sortableItems.sort((a: any, b: any) => {
const aValue = get(a, sortConfig.key);
const bValue = get(b, sortConfig.key);
if (aValue < bValue) {
return sortConfig.direction === 'ascending' ? -1 : 1;
}
if (aValue] > bValue) {
return sortConfig.direction === 'ascending' ? 1 : -1;
}
return 0;
});
And you're good to go for most cases. I don't know how "wild" your data can get so make sure to test a bunch of scenarios.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.