1

I need to create a function for sorting an array of objects. Array looks something like this:

array=[ 0: {id: "7", name: "Aaaaa", level: "3/4", generation: "2", area: "JavaScript", …} 1: {id: "6", name: "Bbbbbb", level: "2/4", generation: "2", area: "JavaScript", …} 2: {id: "10", name: "DEV #1", level: "1/1", generation: "1", area: "Development", …} ]

this is how i call the function

sortHandle={() => this.sortArray("someProperty", "desc/asc")}

i've managed to make it work but only after i click on sorter the second time.

sortCourses(sorter, asc) {
        let courses = this.state.courses;
        let sortedCourses = []
        switch (sorter) {
            case 'level':
                sortedCourses.push(courses.sort((a, b) => { return (parseInt(a.level) - parseInt(b.level)) ? 1 : -1 }))     
                break;
            case 'generation':
                sortedCourses.push(courses.sort((a, b) => { return (parseInt(a.generation) - parseInt(b.generation)) ? 1 : -1 }))
                break;
            case 'name':
                sortedCourses.push(courses.sort((a, b) => { return (a.name.toLowerCase() - b.name.toLowerCase()) ? 1 : -1 }))
            case 'area':
                sortedCourses.push(courses.sort((a, b) => { return (a.area.toLowerCase() - b.area.toLowerCase()) ? 1 : -1 }))
                break;
            case 'date':
                sortedCourses.push(courses.sort((a, b) => { return (a.startDate - b.startDate) ? 1 : -1 }))
                break;
        }
        this.setState({ courses: sortedCourses[0] }, () => { console.log(this.state.courses) })

        if (asc) {
            return sortedCourses[0]
        }
        else if (!asc) {
            return sortedCourses[0].reverse()
        }
    }

I get sortProperty and asc/desc from props sortHandle which is a function in child component.

sortHandle={(sorter,asc) => this.sortCourses(sorter,asc)}

and in my child component sorters look like this:

<span className="margin-h-10 row align-center padding-r-5 pointer"
                        onClick={() => {
                            this.setState({ asc: !this.state.asc });
                            this.props.sortHandle('name', this.state.asc)
                        }}>
                        <span className="margin-r-5">Name</span>
                        <FontAwesomeIcon
                            icon={this.state.asc ? 'chevron-up' : 'chevron-down'}
                            style={{ color: "#7f7f7f", fontSize: "10px" }}
                        />
                    </span>
                    <span className="margin-h-10 row align-center padding-r-5 pointer"
                        onClick={() => {
                            this.setState({ asc: !this.state.asc });
                            this.props.sortHandle('date', this.state.asc)
                        }}>
                        <span className="margin-r-5">Date</span>

This seems to work fine but only after i click sorter 2 times. I guess it has something to do with state. Any solutions?

4
  • if you arent against using a package, thenby is decent for sorting needs Commented Aug 24, 2020 at 11:04
  • use simple sort: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… and then set state to the sorted array: this.setState([...array].sort(...)) Commented Aug 24, 2020 at 11:05
  • The simple sort will not work right for numbers Commented Aug 24, 2020 at 11:06
  • Shameless self-plug: I wrote a helper for sort functions that might be handy: npmjs.com/package/ts-comparer-builder Commented Aug 24, 2020 at 11:08

2 Answers 2

1

maybe you need something like this

The list is your array

the key is key with which you want to sort it

and isAsc is true if you want to sort asc or false if you want to sort with desc

compareStr = key => (a, b) => {
    if (a[key] < b[key]) {
        return -1
    }
    if (a[key] > b[key]) {
        return 1
    }
    return 0
}

sortArray = (list, key, isAsc) => {
    if (!list || !list.length) return []
    const duplicate = [...list]
    if (typeof duplicate[0][key] === 'number' || Number(duplicate[0][key])) {
        duplicate.sort((a, b) =>
            isAsc
                ? Number(a[key]) - Number(b[key])
                : Number(b[key]) - Number(a[key])
        )
        return duplicate
    }
    duplicate.sort(compareStr(key))
    if (isAsc) return duplicate
    return duplicate.reverse()
}

This will work fine if, in your array, your numbers will be numbers, not strings. So you can or change numbers types or you can add your own check for number case instead of this if (typeof duplicate[0][key] === 'number') {

Sign up to request clarification or add additional context in comments.

1 Comment

This helped a lot. I modified my code to just return sortedArray.reverse() if order==='desc' and it works
0

Here's a neat little trick. You don't need to do the sorting in the event handler - you can just do it in your render, and only maintain the information about the sort property and order. Additionally, you only need to code one version of the sorter for each of the properties by just multiplying the sorters' return value with -1 if you want to change the order to descending.

First, I'd extract the sorters themselves:

const sorters = {
  level: (a, b) => parseInt(a.level) - parseInt(b.level),
  generation: (a, b) => parseInt(a.generation - b.generation),
  name: (a, b) => a.name.localeCompare(b.name),
  area: (a, b) => a.area.localeCompare(b.area),
};

Easy enough. Then, I'd put these into your state, along with the sort order:

const SortOrder = {
  ASC: 1,
  DESC: -1,
};

// ...

state = {
  sortOrder: SortOrder.ASC,
  sortProperty: "level",
  array: [],
  // ...
};

Then, in render(), you just take your array and sort it according to the sorter:

render() {
  const { array, sortProperty, sortOrder } = this.state;
  const arraySorter = sorters[sortProperty];
  const displayArray = [...array].sort(
    (a, b) => arraySorter(a, b) * sortOrder
  );

  return (
    <>
      {displayArray.map((item) => (
        <p key={item}>{item}</p>
      ))}
    </>
  );
}

Notice how we take the correct sorter function from sorters, and multiply it by the current sortOrder. This lets us change the ascending/descending order by just changing between 1 and -1, as with -1, the order is reversed.

Then finally, in sortArray, we just update the state with the new sort property and sort order:

sortArray = (property, order) => {
  this.setState({
    sortProperty: property,
    sortOrder: order === "asc" ? SortOrder.ASC : SortOrder.DESC,
  });
};

Here the API is the same as with yours, i.e. () => this.sortArray('level', 'desc'), but honestly you could just pass order directly to sortOrder and call it like () => this.sortArray('level', SortOrder.DESC). Or drop the order entirely from the function since you could now change it separately from the property.

All together:

const SortOrder = {
  ASC: 1,
  DESC: -1,
};

const sorters = {
  level: (a, b) => parseInt(a.level) - parseInt(b.level),
  generation: (a, b) => parseInt(a.generation - b.generation),
  name: (a, b) => a.name.localeCompare(b.name),
  area: (a, b) => a.area.localeCompare(b.area),
};

class MyComponent extends React.Component {
  state = {
    sortOrder: SortOrder.ASC,
    sortProperty: "level",
    array: [],
    // ...
  };

  sortArray = (property, order) => {
    this.setState({
      sortProperty: property,
      sortOrder: order === "asc" ? SortOrder.ASC : SortOrder.DESC,
    });
  };

  render() {
    const { array, sortProperty, sortOrder } = this.state;
    const arraySorter = sorters[sortProperty];
    const displayArray = [...array].sort(
      (a, b) => arraySorter(a, b) * sortOrder
    );

    return (
      <>
        {displayArray.map((item) => (
          <p key={item}>{item}</p>
        ))}
      </>
    );
  }
}

1 Comment

The thing with this function () => this.sortArray('level', 'desc') is that i need to get function params from state of child component. Means that when i click on some sorter in child component it trigers onClick event that passes its state (property, order) to function sortArray in parent component. Sorry if i didn't explain it very well. I'm still learning react and my english is not perfect

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.