skip to Main Content

I have this data which i’m displaying in the table

[
    {
        "ID": 9,
        "Status": "Pending",
    },
    {
        "ID": 8,
        "Status": null,
    },
    {
        "ID": 7,
        "Status": "Pending",
    },
    {
        "ID": 10,
        "Status": null,
    },
    {
        "ID": 18,
        "Status": "Completed",
    },
    {
        "ID": 17,
        "Status": "In Progress",
    }
]

Sort Method:

Ascending order :

 this.List.sort((a, b) => a[columnname] < b[columnname] ? 1 : a[columnname] > b[columnname]` ? -1 : 0);

Descending order :

 this.List.sort((a, b) => a[columnname] > b[columnname] ? 1 : a[columnname] < b[columnname] ? -1 : 0);

and using sort function to sort the data in table issue is when i sort it, i want to place null values at the last. In current scenario they are in between other values

Like

Completed
null
In Progress
null
Pending 
null 
..

2

Answers


  1. Couple of things for your sorting function

    • You should not use < and > to compare strings. It does weird things with upper/lower case, special characters, numbers, etc. there is a String.localeCompare function that can be used to compare strings
    • If you want to sort null (or other special values) always at the end, just let it return a custom value that is always the same

    This is a sample sorting function, that contains a check for number & string and always sorts null values together

    function mySort(data, columnName, reverse) {
        return data.sort((a, b) => {
            const valueA = reverse ? a[columnName] : b[columnName];
            const valueB = reverse ? b[columnName] : a[columnName];
    
            if (valueA === null) {
                return -1;
            }
            if (typeof valueA === 'string' && typeof valueB === 'string') {
                return valueA.localeCompare(valueB);
            }
            if (typeof valueA === 'number' && typeof valueB === 'number') {
                return valueA - valueB;
            }
        })
    }
    

    if you call mySort(list, 'Status', false) you will notice that all the null values are at the end while the rest is still properly sorted.

    If you want to have null in the end for both descending and ascending you can adapt the special === null check to something like

    if (valueA === null) {
       return reverse ? 1 : -1;
    }
    
    Login or Signup to reply.
  2. A fairly short way to do this would be:

    const tasks = [
      { "ID":  9, "Status": "Pending"     },
      { "ID":  8, "Status":  null         },
      { "ID":  7, "Status": "Pending"     },
      { "ID": 10, "Status":  null         },
      { "ID": 18, "Status": "Completed"   },
      { "ID": 17, "Status": "In Progress" },
    ];
    
    const columnName = "Status";
    tasks.sort(({ [columnName]: a }, { [columnName]: b }) => (
      (a == null) - (b == null) // move null values to the back of the array
      || -(a < b)               // move `a` to the front if it's smaller than `b`
      || +(a > b)               // move `a` to the back if it's greater than `b`
    ));
    
    console.log(tasks);

    In the snippet above we start with destructuring both comparator objects. ({ [columnName]: a }, { [columnName]: b }) stores the value of the columnName property of the first argument in the variable a and b for the second argument.

    We then start with (a == null) - (b == null) to move the null values to the back of the array. This works because true coerces into 1 and false coerces into 0. So if a is null and b is not, then true - false will evaluate to 1 - 0 //=> 1. A positive return value means that a is placed behind b. If we are in the opposite scenario and a is not null but b is, then false - true //=> -1. A negative return value means that a is placed before b.

    Both 1 or -1 are truthy values, which cause the OR operator || to short circuit and return the first operand. If both values are null or both values are not null, then we end up with true - true or false - false which both evaluate to 0. Since 0 is a falsy value, we’ll move on to the second operand of the OR operator.

    This brings us to -(a < b), which checks if a is smaller than b. When this is true -true will evaluate into -1, so a is placed before b. If this is false then -false will evaluate into -0, which is a falsy value and moves us to the next operand in the OR chain. Assume that a and b are both null, then null < null is false, thus moving us on to the next OR operand. If a and b are both not null, say the status string, then the strings are compared based on code point values and -1 is returned if a is smaller than b. Otherwise we move on to the final OR operand.

    +(a > b) is very similar to -(a < b), but returns 1 if a is greater than b. Placing a after b in the result.

    Here are 6 example scenarios, showing the different outputs:

    a = null, b = null
    (a == null) - (b == null) || -(a < b) || +(a > b) /*
    └─────────  0  ─────────┘    └─ -0 ─┘    └─  0 ─┘ */
    //=>  0   a and b are equal
    
    a = null, b = "a"
    (a == null) - (b == null) || -(a < b) || +(a > b) /*
    └─────────  1  ─────────┘                         */
    //=>  1   a is placed after b
    
    a = "a", b = null
    (a == null) - (b == null) || -(a < b) || +(a > b) /*
    └─────────  -1  ─────────┘                        */
    //=> -1   a is placed before b
    
    a = "a", b = "a"
    (a == null) - (b == null) || -(a < b) || +(a > b) /*
    └─────────  0  ─────────┘    └─ -0 ─┘    └─  0 ─┘ */
    //=>  0    a and b are equal
    
    a = "a", b = "b"
    (a == null) - (b == null) || -(a < b) || +(a > b) /*
    └─────────  0  ─────────┘    └─ -1 ─┘             */
    //=> -1    a is placed before b
    
    a = "b", b = "a"
    (a == null) - (b == null) || -(a < b) || +(a > b) /*
    └─────────  0  ─────────┘    └─ -0 ─┘    └─  1 ─┘ */
    //=>  1    a is placed after b
    

    A less cryptic version could be written like so:

    tasks.sort(({ [columnName]: a }, { [columnName]: b }) => {
      if (a == null && b != null) return  1; // a after b
      if (a != null && b == null) return -1; // a before b
      if (a < b) return -1; // a before b
      if (a > b) return  1; // a after b
      return 0; // equal
    });
    

    To apply an asc/desc direction, but keep the null values at the back of the array, you can apply a modifier to the second part to inverse the sign (in case of a descending direction).

    const modifiers = { asc: 1, desc: -1 };
    const columnName = "Status";
    const direction = "desc";
    
    tasks.sort(({ [columnName]: a }, { [columnName]: b }) => (
      (a == null) - (b == null) // move null values to the back of the array
      || modifiers[direction] * (
        -(a < b)                // move `a` to the front if it's smaller than `b`
        || +(a > b)             // move `a` to the back if it's greater than `b`
      )
    ));
    
    // without comments
    tasks.sort(({ [columnName]: a }, { [columnName]: b }) => (
      (a == null) - (b == null) || modifiers[direction] * (-(a < b) || +(a > b))
    ));
    

    Or:

    tasks.sort(({ [columnName]: a }, { [columnName]: b }) => {
      if (a == null && b != null) return  1; // a after b
      if (a != null && b == null) return -1; // a before b
      if (a < b) return -1 * modifiers[direction]; // a before b
      if (a > b) return  1 * modifiers[direction]; // a after b
      return 0; // equal
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search