4

I'm following the ReactJS AJAX and APIs tutorial. I wrote a simple API in Spring, then a React component to consume that API at http://localhost:8080. The API currently returns a list of two items as follows:

[
    {brand:"Asus", make:"AAA"},
    {brand:"Acer", make:"BBB"}
]

Here's what my component looks like:

import React from 'react';
import ReactDOM from 'react-dom';

import { environment } from '../environment/environment';

export class ComputerList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      error: null,
      isLoaded: false,
      items: [
        {brand: null, make: null}
      ]
    };
  }

  componentDidMount() {
    fetch("http://localhost:8080/computers")
    .then(res => res.json())
    .then(
      (result) => {
        // correctly displays the results
        console.log(result);
        this.setState({
          isLoaded: true,
          items: result.items
        });
      },
      (error) => {
        this.setState({
          isLoaded: true,
          error
        });
      }
    )
  }

  render() {
    const { error, isLoaded, items } = this.state;

    if(error) {
      return(<div>Error: {error.message}</div>);
    }
    else if(!isLoaded) {
      return(<div>Loading...</div>);
    }
    else if(items) {
      console.log(items);
      // error here: cannot read property "map" of undefined
      // because this.state.items is undefined somehow?
      return(
        <ul>
          {items.map(item => (
            <li key={item.make}>{item.brand} {item.make}</li>
          ))}
        </ul>
      );
    }
  }
}

At line 24, the results are successfully retrieved and logged.

But at line 54, when I try to map each result to a <li> item, the TypeError is thrown because items is somehow undefined? I've followed the answer to a similar question by initializing items at line 12, and checking items at line 48, to no avail.

How can I fix this?

Jason
  • 409
  • 2
  • 6
  • 10

3 Answers3

2

This is likely due to the type of items being something other than an array, which is why the map() method would not be defined.

For a more robust render() method, you can replace else if(items) { with else if(Array.isArray(items)) {, which should protect against the error message you're seeing:

render() {
    const { error, isLoaded, items } = this.state;

    if(error) {
      return(<div>Error: {error.message}</div>);
    }
    else if(!isLoaded) {
      return(<div>Loading...</div>);
    }
    else if(Array.isArray(items)) { // <-- update here
      console.log(items);
      // error here: cannot read property "map" of undefined
      // because this.state.items is undefined somehow?
      return(
        <ul>
          {items.map(item => (
            <li key={item.make}>{item.brand} {item.make}</li>
          ))}
        </ul>
      );
    }
  }

Hope that helps!

Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
  • When I did this, I got the error `Nothing was returned from render`. If nothing was returned, that means none of the conditions were met - i.e. `items` probably isn't of type Array. I'll have to look into this. – Jason Sep 27 '18 at 03:14
  • 1
    @Jason its looking like the type of `items` is something other than an array. Can you replay `console.log(result);` with `console.log(typeof result.items)` and let me know what is logged? – Dacre Denny Sep 27 '18 at 03:19
  • 1
    Was able to figure it out thanks to your advice. At line 27, I changed `items:result.items` to `items:result`. The former expects an Object named `items`, while the latter simply saves the response (which is an Array of objects). After changing it, the info was rendered correctly. – Jason Sep 27 '18 at 03:21
1

Thanks to @DacreDenny for the advice :)

Line 27: items: result.items. This line expects the response to contain an Object named "items".

But my API only returns an Array of objects. So I changed the line to

Line 27 to: items: result. This saves the entire array to state.items. Then it can be properly mapped and rendered.

Jason
  • 409
  • 2
  • 6
  • 10
0

JUst do this. Avoid if loops in render instead use && operator or ternary operator directly in return to manage if checks

  return(
    <ul>
      {!isLoaded && <div>Loading...</div>}
       {error && !isLoaded && <div>Error: {error.message}</div>)}
      {!isLoaded && items && items.map(item => (
        <li key={item.make}>{item.brand} {item.make}</li>
      ))}
    </ul>
  );

Also your make key in object initially contains null value hence you cannot assign that as a key. Instead what you can do is try to generate unique id for object in your array from the backend or else use something like below

 return(
    <ul>
      {items && items.map((item, index) => (
        <li key={"key"+index}>{item.brand} {item.make}</li>
      ))}
    </ul>
  );

Excuse me for typo issues because I am answering from my mobile

Hemadri Dasari
  • 32,666
  • 37
  • 119
  • 162
  • Thanks for the advice! I'll take note of that extra validation. Regarding the && operator, is there a reason to use it over `if` statements? I find that `if` statements are easier to read (for me, anyway). – Jason Sep 27 '18 at 03:36
  • 1
    You should keep your render part as much as clean. Since you are new to && operator ternary operator you feel if else is easy but when you start implementing with these operator you will feel this is more convenient than if else. Say suppose you need to do 10 if else checks in the component ? Would you check all of them in render part that will look clumsy code. So basically these two operators are heavily used in React for managing if else and they are just simple :) – Hemadri Dasari Sep 27 '18 at 03:43