Hugh Mungus
Hugh Mungus

Reputation: 129

ReactJS: Data from Express displays as numbers instead of JSON

I'm trying to send strings from my Express server as a JSON object, and render those JSON objects in my React app.

Right now it is just displaying a number for each of the 25 elements for some reason:

0123456789101112131415161718192021222324

In my Express app image_url and post_url are strings. This always returns strings:

  const image_url = imageData.data[0].media.image.src;
  const post_url = imageData.data[0].target.url;
  globalImageList.push({
    image: image_url,
    post: post_url
  });
}

Also in Express. This sends the object to the React app when this function's url, i.e. '/api' is visited.

if (resultCount === processList.length) {
  res.send(JSON.stringify(JSON.parse(globalImageList)));

In my React app, my component starts with state where the apiResponse is an empty string:

this.state = {
  apiResponse: ""

Data is fetched from my Express app using fetch() which is called in componentDidMount. I store that JSON data in the apiResponse field:

callAPI() {
    fetch('/api/getList')
        .then(res => res.json())
        .then(res => this.setState({ apiResponse: res }))
}

The way I render that state is as shown and it does not show the URLs, it just enumerates the objects:

render() {
  return(
    <div className="App">
      <p className="App-intro">{Object.keys(this.state.apiResponse).map(i => i)}</p>
    </div>
  )
}

If I write it as {(this.state.apiResponse).map(i => i)} I get an error that apiResponse is not a function.
If I write it as {this.state.apiResponse} I get an error that says "Objects are not valid as a React child (found: object with keys {image, post}). If you meant to render a collection of children, use an array instead."

I've also found I can modify .then(res => res.json()) to .then(res => res.text()) and render only {this.state.apiResponse} the text of all the strings will show on the screen, but I need control of the individual elements in JSON format (I believe).

Upvotes: 1

Views: 195

Answers (1)

Dacre Denny
Dacre Denny

Reputation: 30390

There are a few issues here - one seems to be that the component is not correctly handling/rendering the "loading stage" of the data fetch sequence (ie, the point where fetch() is busy downloading data from the server).

This problem is usually solved by displaying a loading message to the user while the fetch() is in progress, rather than attempt to render the component as if the response data were already present. A simple solution in your case would be to make the following changes:

callAPI() {
    /* Leave as is - use res.json() to parse response to JSON object */
    fetch('/api/getList')
        .then(res => res.json())
        .then(res => this.setState({ apiResponse: res }))
}

render() {
  /* Extract apiResponse object (optional) */
  const { apiResponse } = this.state;

  /* Assume that while apiResponse matches initial state, the fetch
  request is still busy, so render a loading message */
  if(!apiResponse) {
      return <p>Loading</p>
  }

  /* Otherwise, we assume the apiResponse is populated with data from
  the sever and proceed to enumerate and render */
  return(
    <div className="App">
      <p className="App-intro">
      {/* Enumerate values of response, render as strings */}
      {Object.values(apiResponse).map((value, index) => 
          (<p key={index}>{ JSON.stringify(value) }</p>)
      )}
      </p>
    </div>
  )
}

Note also that enumeration is over Object.values() rather than Object.keys() - this allows you to map the actual values of the apiResponse object to a rendered result (rather than number keys). Hopefully that points you in the right direction :-)

Upvotes: 1

Related Questions