0

Im trying to filter data with multiple key value in reactjs with tsx. Cards.tsx is a parent component and ShipmentCard.tsx is a child. I'm getting Property 'toLowerCase' does not exist on type 'never' error. I just want to return the related object based on search criteria. Can anyone let me know where I made a mistake?

Cards.tsx

class Cards extends Component {
  state = {
    card: [],
    filter: ""
  };

  componentDidMount() {
    this.loadShipmentList();
  }

  handleChange = (event: any) => {
    this.setState({ filter: event.target.value });
  };

  loadShipmentList() {
    fetch("http://localhost:3001/shipments")
      .then(response => response.json())
      .then(data => this.setState({ card: data }));
  }

  render() {
    const { card, filter } = this.state;
    const lowercasedFilter = filter.toLowerCase();
    const filteredData = this.state.card.filter(item => {
      return Object.keys(item).some(key =>
        item[key].toLowerCase().includes(lowercasedFilter)
      );
    });

    return (
        <Card className="Search-Bar">
          <CardContent>
            <Grid container spacing={3}>
                <TextField
                  label="Search"
                  onChange={this.handleChange}
                />
            </Grid>
          </CardContent>
        </Card>
        <Grid container spacing={3}>
          {filteredData.map((card: any) => (
            <Grid item xs={12}>
              <ShipmentCard key={card.id} value={card} />
            </Grid>
          ))}
        </Grid>
    );
  }
}

export default Cards;
db.json

{
  "shipments": [
    {
      "id": "123",
      "name": "Heromisha",
      "total": "1000",
      "status": "ACTIVE",
      "userId": "E222"
    },
    {
      "id": "456",
      "name": "Honda",
      "total": "3000",
      "status": "ACTIVE",
      "userId": "E111"
    }
  ]
}
SKL
  • 1,243
  • 4
  • 32
  • 53
  • When does this error occur, upon startup? Also, I would suggest not using the word 'filter' for your variable. – rrd Aug 06 '19 at 07:18
  • noted that. when start typing. `TypeError: item[key].toLowerCase is not a function` – SKL Aug 06 '19 at 07:24
  • @rrd - There's no reason you can't use `filter` as a variable name. – T.J. Crowder Aug 06 '19 at 07:57
  • @T.J.Crowder, thanks for coming back. I'm still figuring on it. `item[key].toLowerCase()` object value and `filter.toLowerCase()` is input value. This is error that im getting `TypeError: item[key].toLowerCase is not a function` – SKL Aug 06 '19 at 13:09
  • @SKL - You won't get that error if you do what I added to the end of my answer. – T.J. Crowder Aug 06 '19 at 13:19
  • Last approach thrown this error `Binding element 'name' implicitly has an 'any' type` – SKL Aug 06 '19 at 13:26

2 Answers2

2

Component is a generic interface with two type parameters: The type of your props, and the type of your state. Your component doesn't seem to have props so you can just use object or {} for the props interface, but you do have state, so you need to say what shape that state has:

interface Card {
  id:     string;
  name:   string;
  total:  string;
  status: string;
  userId: string;
}
interface CardsState {
  card:   Card[];
  filter: string;
}
class Cards extends Component<object,CardsState> {
  state: CardsState = {
    card: [],
    filter: ""
  };
  // ...
}

Separately, the next couple of problems I suspect you'll have are:

  1. You're not checking that fetch succeeded. You're not alone in this, it's a very common error (so common I wrote it up on my anemic little blog). You need to add a check to your fetch call:

    fetch("http://localhost:3001/shipments")
    .then(response => {
      if (!response.ok) {
        throw new Error("HTTP error " + response.status);
      }
      return response.json();
    })
    // ...
    
  2. You're not handling errors from fetch. loadShipmentList should either return the promise chain (and then componentDidMount and other places it's used would handle errors) or handle/report errors itself.

  3. You're setting card to the result of parsing your JSON. Your code assumes card is an array, but the JSON's top level isn't an array, it's an object with a shipments property (which is an array). I suspect you meant to set card (which should be cards, plural, or shipments) to the shipments property of the returned data:

    .then(({shipments}) => this.setState({ card: shipments }));
    //    ^^         ^^                          ^^^^^^^^^
    

There may be further issues, but the main answer above should address the never issue, and hopefully these further hints have helped.


In a comment you've said that when you give card a type, this code causes an error:

const filteredData = this.state.card.filter(item => {
  return Object.keys(item).some(key =>
    item[key].toLowerCase().includes(lowercasedFilter)
  );
});

If you expect the shape of the data in the JSON to be variable, you could declare card as any[] (or perhaps Record<string,string>[]) and then that dynamic code would work. But only if you want the component driven by the JSON. Otherwise, update the code to use the typesafe property names:

const filteredData = this.state.card.filter(({id, name, total, status, userId}) =>
  `${id}\t${name}\t${total}\t${status}\t${userId}`.toLowerCase().includes(lowercasedFilter)
);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • im quite confused with your third point. When i start typing `TypeError: item[key].toLowerCase is not a function` error thrown – SKL Aug 06 '19 at 07:19
  • 1
    @SKL - I'm not sure how that relates to my third point, but if you want to access the properties on entries in `card` dynamically like that, you'll have to tell TypeScript you want to do that. This is a fairly basic TypeScript thing, nothing to do with React, and getting a bit far afield of the original question. I've added a bit to the end of the answer about doing that. – T.J. Crowder Aug 06 '19 at 07:43
2

The compiler doesn't real type of state.card[...], becouse it's declared as array. The most simple way is to declare this as any[]

  state = {
    card: [] as any[],
    filter: ""
  };

Or describe data type explicit

interface Card {
    "id": string,
    "name": string,
    "total": string,
    "status": string,
    "userId": string
}
...
      state = {
        card: [] as Card[],
        filter: ""
      };

Or by object example

const cardSample = {
      "id": "123",
      "name": "Heromisha",
      "total": "1000",
      "status": "ACTIVE",
      "userId": "E222"
  };

type Card = typeof cardSample;

    ...
          state = {
            card: [] as Card[],
            filter: ""
          };
Nail Achmedzhanov
  • 1,264
  • 1
  • 9
  • 12
  • @T.J.Crowder I've checked the code in the playground and it is compiled well – Nail Achmedzhanov Aug 06 '19 at 10:38
  • 1
    I checked in playground without React typing. Now I've cheked it in local project that created by react-create-app, and the changes fix the error "Property 'toLowerCase' does not exist on type 'never'". – Nail Achmedzhanov Aug 06 '19 at 12:45
  • [Without fix ](https://www.typescriptlang.org/play/#code/MYGwhgzhAEDCYCcAmMCmAPALqgdiuA9gLYAOBOum0A3gFDTQSZjbQC8N9D0wiSAXNADaAXQA0XBgDMAliGwJBAIiVcAvgG5a9BLiSoEACgCUnbj3JMaPPmOiz5B6GvbRMACxkQAdExaotc2BLKhACAHcDXghUJAAxOQVXBwVvTAIAGQiDeBiTQO5gnCsUg1iAERYwVw8vX2Zsb15kb1KjGWwidgA+M3NoXUwAVwQcaAB5ACMAK1RgTG8Aa1QATwhDDtQiY19iVENllZ7Jfs2iIUORNMzshFz9nZkcUCH9dbDIhGjYhMcEYxODGMBQYamB2nMgxGYwA5DD1LQ1NoMGQEFR9FIwEN5HA+BANEA) – Nail Achmedzhanov Aug 06 '19 at 12:46
  • With fix https://www.typescriptlang.org/play/#code/MYGwhgzhAEDCYCcAmMCmAPALqgdiuA9gLYAOBOum0A3gFDTQSZjbQC8N9D0wiSAXNADaAXWiRxOAJ6iANFwYAzAJYhsCQQCJNXAL4BuWvQS4kqBAAoAlJ249yTGjz6zoKteei720TAAtlCAA6JhZUQztgByoQAgB3c14IVCQAMVV1H3d1IMwCABl483hk6wjuKJxHbPMUgBEWMB9-QJDmbCDeZCCay2VsInYAPls7aBNMAFcEHGgAeQAjACtUYEwggGtUKQgLftQiKxDiVAstqWGFMf2iIXORXIKihBLTo+UcUEmzXdiEhCSKXSHgQViuDCs5QYukhRjsE2mswA5Ei9LRdEYMGQEFQzIowJM1HA+BB9EA – Nail Achmedzhanov Aug 06 '19 at 12:46
  • Nice! Ah, we're talking about different calls to `toUpperCase`! :-) That explains it. – T.J. Crowder Aug 06 '19 at 12:58