admin管理员组

文章数量:1404923

I have a React table that has sortable headers by desc and asc values. It parses string values to numbers for sorting. However, my numeric(x) function fails to deliver when it meets a null value in my dataset.

Here is the error: TypeError: Cannot read property 'slice' of null

Below is my function, and I also added code on how I implement numeric to my Comparator() for sorting

function numeric(x) {
    const val = parseFloat(x);
    if (isNaN(val)) {
      return parseFloat(x.slice(1));
    } else {
      return val;
    }
  }

function descendingComparator(a, b, orderBy)
{
    const numericA = numeric(a[orderBy]);
    const numericB = numeric(b[orderBy]);
    if (numericB < numericA){
        return -1
    }
    if (numericB > numericA){
        return 1
    }
    return 0
}

How should I handle nulls in my numeric function? Realistically, they should be ignored and be placed at the bottom of the pecking order when sorting by asc and desc.

EDIT: example data types for input x:

  • 10
  • 10%
  • $10
  • .1
  • abc (string characters)

If not careful, the Null values can act as a 0 when sorting data. It's important to note that Null simply means no data available, they should not be given a numerical value.

EDIT EDIT: Important information about my Comparator and sorting functions

function getComparator(order, orderBy)
{
    return order === "desc" 
        ? (a, b) => descendingComparator(a, b, orderBy)
        : (a, b) => -descendingComparator(a, b, orderBy)
}

const sortedRowInformation = (rowArray, parator) =>
{
    const stabilizedRowArray = rowArray.map((el, index) => [el, index])
    stabilizedRowArray.sort((a, b) =>
    {
        const order = parator(a[0], b[0])
        if (order !== 0) return order
        return a[1] - b[1]
    })
    return stabilizedRowArray.map((el) => el[0])
}

An example of my table that uses all of these functions pieced together:

export default function TableContent(props)
{
    const [orderDirection, setOrderDirection] = useState('asc');
    const [valueToOrderBy, setValueToOrderBy] = useState('symbol');

    const { data } = props;
    
    const handleRequestSort = (event, property) =>
    {
        const isAscending = (valueToOrderBy === property && orderDirection === 'asc') 
        setValueToOrderBy(property)
        setOrderDirection(isAscending ? 'desc' : 'asc')
    }

    return (
        <>
            <TableContainer>
                <Table>
                    <AdvancedStatsHeaders
                        data={data}
                        valueToOrderBy={valueToOrderBy}
                        orderDirection={orderDirection}
                        handleRequestSort={handleRequestSort}
                    />
                    <TableBody>
                    {
                        sortedRowInformation(data, getComparator(orderDirection, valueToOrderBy))
                            .map((stock, index) => (
                            <TableRow key = {index} >
                                <TableCell>
                                    {stock.symbol}
                                </TableCell>
                                <TableCell>
                                    {stock.enterprisevalue}
                                </TableCell>
                                <TableCell>
                                    {stock.enterprise_value_revenue}
                                </TableCell>
                                <TableCell>
                                    {stock.revenuepershare}
                                </TableCell>
                                <TableCell>
                                    {stock.debt_to_equity}
                                </TableCell>
                                <TableCell>
                                    {stock.ebitda}
                                </TableCell>
                                <TableCell>
                                    {stock.profitmargin}
                                </TableCell>
                                <TableCell>
                                    {stock.price_to_sales}
                                </TableCell>
                                <TableCell>
                                    {stock.price_to_book}
                                </TableCell>
                                <TableCell>
                                    {stock.put_call_ratio}
                                </TableCell>
                            </TableRow>
                        ))
                    }
                    </TableBody>
                </Table>
            </TableContainer>
        </>
    );
}

I have a React table that has sortable headers by desc and asc values. It parses string values to numbers for sorting. However, my numeric(x) function fails to deliver when it meets a null value in my dataset.

Here is the error: TypeError: Cannot read property 'slice' of null

Below is my function, and I also added code on how I implement numeric to my Comparator() for sorting

function numeric(x) {
    const val = parseFloat(x);
    if (isNaN(val)) {
      return parseFloat(x.slice(1));
    } else {
      return val;
    }
  }

function descendingComparator(a, b, orderBy)
{
    const numericA = numeric(a[orderBy]);
    const numericB = numeric(b[orderBy]);
    if (numericB < numericA){
        return -1
    }
    if (numericB > numericA){
        return 1
    }
    return 0
}

How should I handle nulls in my numeric function? Realistically, they should be ignored and be placed at the bottom of the pecking order when sorting by asc and desc.

EDIT: example data types for input x:

  • 10
  • 10%
  • $10
  • .1
  • abc (string characters)

If not careful, the Null values can act as a 0 when sorting data. It's important to note that Null simply means no data available, they should not be given a numerical value.

EDIT EDIT: Important information about my Comparator and sorting functions

function getComparator(order, orderBy)
{
    return order === "desc" 
        ? (a, b) => descendingComparator(a, b, orderBy)
        : (a, b) => -descendingComparator(a, b, orderBy)
}

const sortedRowInformation = (rowArray, parator) =>
{
    const stabilizedRowArray = rowArray.map((el, index) => [el, index])
    stabilizedRowArray.sort((a, b) =>
    {
        const order = parator(a[0], b[0])
        if (order !== 0) return order
        return a[1] - b[1]
    })
    return stabilizedRowArray.map((el) => el[0])
}

An example of my table that uses all of these functions pieced together:

export default function TableContent(props)
{
    const [orderDirection, setOrderDirection] = useState('asc');
    const [valueToOrderBy, setValueToOrderBy] = useState('symbol');

    const { data } = props;
    
    const handleRequestSort = (event, property) =>
    {
        const isAscending = (valueToOrderBy === property && orderDirection === 'asc') 
        setValueToOrderBy(property)
        setOrderDirection(isAscending ? 'desc' : 'asc')
    }

    return (
        <>
            <TableContainer>
                <Table>
                    <AdvancedStatsHeaders
                        data={data}
                        valueToOrderBy={valueToOrderBy}
                        orderDirection={orderDirection}
                        handleRequestSort={handleRequestSort}
                    />
                    <TableBody>
                    {
                        sortedRowInformation(data, getComparator(orderDirection, valueToOrderBy))
                            .map((stock, index) => (
                            <TableRow key = {index} >
                                <TableCell>
                                    {stock.symbol}
                                </TableCell>
                                <TableCell>
                                    {stock.enterprisevalue}
                                </TableCell>
                                <TableCell>
                                    {stock.enterprise_value_revenue}
                                </TableCell>
                                <TableCell>
                                    {stock.revenuepershare}
                                </TableCell>
                                <TableCell>
                                    {stock.debt_to_equity}
                                </TableCell>
                                <TableCell>
                                    {stock.ebitda}
                                </TableCell>
                                <TableCell>
                                    {stock.profitmargin}
                                </TableCell>
                                <TableCell>
                                    {stock.price_to_sales}
                                </TableCell>
                                <TableCell>
                                    {stock.price_to_book}
                                </TableCell>
                                <TableCell>
                                    {stock.put_call_ratio}
                                </TableCell>
                            </TableRow>
                        ))
                    }
                    </TableBody>
                </Table>
            </TableContainer>
        </>
    );
}

Share Improve this question edited Feb 28, 2021 at 19:58 yung peso asked Feb 25, 2021 at 18:45 yung pesoyung peso 1,8068 gold badges41 silver badges88 bronze badges 2
  • I don't understand why you are doing that slice, but one way of avoiding this specific error is to do something like this: if (x && isNaN(val)). Or you can have a try-catch block. – Håken Lid Commented Feb 25, 2021 at 18:55
  • @HåkenLid, I'm not sure what to return after that statement. – yung peso Commented Feb 25, 2021 at 19:04
Add a ment  | 

3 Answers 3

Reset to default 6 +50

The challenge is about sorting numbers OR text with the same function, based on a column name and a direction. The difficulty is about making sure we deal with a number or with text... to apply the right .sort() callback.

Additionally, there are two things to take in account:

  • Some strings may need to be "reformatted" as numbers
  • Some values may be null and should always must ends up at the end of the sorting.

Okay! First, let's go through your functions from the first one:
sortedRowInformation(data, getComparator(orderDirection, valueToOrderBy)).map(...)

That sortedRowInformation really looks like a .sort() function nested in a .sort() function... With two .map() applied on a stabilizedRowArray sub-array...

I have to admit I discarded it right away. I assumed the expected result by its pretty descriptive name and the 1st argument being the data.

The second argument... Where getComparator() is a function call with the two ponant state properties as argument. That returns two possible functions (lol, the same function with or without a minus sign in front of its evaluation...). So calling that descendingComparator function again calls another function numeric() where our null problem is.

And all this process to just return -1, 0 or 1 to be used as that second argument for sortedRowInformation()... Which is the basic .sort() internal job.

Just describing this process should raise a big flag. You nicely overplicated everything.


Solution

Here are my solution explanations starting from the other end:

The numeric() function is okay to isolate as a function. But that is where you had difficulties due to the null values ing in 3 functions above... So what if we put the null values aside from that logic and always assume a non-null value?

So now that we decided there is no null values, we can test them more easily for number or string. Anyway there is a special case where a string can finally be a number when removing the $ and as (thousand separator).

I came up with this custom isNumeric() function:

function isNumeric(x) {
  let value = !isNaN(x) ? x: parseFloat(x.replace(/[\$,]/g, ""))
  return {isNum:!isNaN(value), value}
}

That function returns an object containing a boolean and a "processed" value: a number or NaN.

Now... Getting back to the starting point for the sorting, here is a function to sort the data:

function sortAll(data, orderDirection, valueToOrderBy) {
  
  // Filter the nulls in an array and the rest in another
  let nulls = data.filter((item) => item[valueToOrderBy] == null)
  let toSort = data.filter((item) => item[valueToOrderBy])
  
  // Sort the non-null values
  let sorted = toSort.sort((a, b) => {
    
    // Check if both values are numeric
    let aa = isNumeric(a[valueToOrderBy])
    let bb = isNumeric(b[valueToOrderBy])
    
    // If numerics
    if (aa.isNum && bb.isNum) {
      return aa.value - bb.value
    }
    
    // If strings
    return (a[valueToOrderBy]).toLowerCase() > (b[valueToOrderBy]).toLowerCase() ? 1 : -1;
  });

  // The sorting direction
  if (orderDirection === "desc") {
    sorted.reverse();
  }
  
  //  Add the nulls at the end of the returned array
  return sorted.concat(nulls);
}

So to apply it in your React ponent return is:

sortAll(data, orderDirection, valueToOrderBy).map(...)

instead of:

sortedRowInformation(data, getComparator(orderDirection, valueToOrderBy)).map(...)

Squarely discard those functions: sortedRowInformation , getComparator and descendingComparator and replace numeric with my isNumeric.

Here is a CodePen where I tested the sorting cases with some feak data.


If it does not already work in all cases with your data... At least, it is way easier to improve. ;)

It's unclear what's your sorting function is. Array.prototype.sort() expects Comparator in the form of

function Comparator(a, b, /* no `orderBy` here! */) { ... }

See docs

and passes items of an array in a and b, not two references to the same array. So the code your've wrote in this question looks wrong.

But maybe that's just a copy-paste error, so let's assume that your're using the standard Array.prototype.sort(). In this case

if( a === null ) return 1
if( b === null ) return -1

should do the trick:

function numeric(x) {
    const val = parseFloat(x);
    if (isNaN(val)) {
      return parseFloat(x.slice(1));
    } else {
      return val;
    }
  }

function descendingComparator(a, b) // removed `orderBy`
{
    if( a === null ) return 1
    if( b === null ) return -1
    const numericA = numeric(a); // removed [orderBy]
    const numericB = numeric(b); // removed [orderBy]
    if (numericB < numericA){
        return -1
    }
    if (numericB > numericA){
        return 1
    }
    return 0
}

const test_arr = [null, 1, null, '3%', 0, '2', "$4", null]

console.log([...test_arr].sort(descendingComparator))

// here's how you can implement behavior of removed `orderBy` arg
const ascendingComparator = (a, b) => descendingComparator(b, a)

console.log([...test_arr].sort(ascendingComparator))
// or simply use `reverse()`

// or, if you want `null` at the and in this case too, then
const ascendingComparator2 = (a, b) => {
    if( a === null ) return 1
    if( b === null ) return -1
    return descendingComparator(b, a)
}

console.log([...test_arr].sort(ascendingComparator2))

You can try something like below:

function numeric(x,order) {
    const val = parseFloat(x);
    if(!x) {
     return order === 'desc'? -1 : 1;
    }
    else {
     if (isNaN(val)) {
       return parseFloat(x.slice(1));
      } else {
       return val;
      }
    }
  }

本文标签: javascriptHow to handle null values inside my sort by functionStack Overflow