0

I'm trying to use an array returned from Node in a React code.

Here's the Node's router code:

router.get('/prodlist', async (req, res) => {
  try {
    await Product.find({}).exec((err, result) => {
      res.status(200).send({ express: result })
    })
  } catch (e) {
    console.log(e)
    res.status(400).send(e)
  }
})

The React's component code:

import React, { useState, useEffect } from 'react';

function App() {
  const [datas, setDatas] = useState(null)

  useEffect(() => {
    var curURL = window.location.href
    var calledRoute = curURL.substr(8).substr(curURL.substr(8).indexOf("/"))
    callBackendAPI(calledRoute)
      .then(res => setDatas(prevData => res.express))
      .catch(err => console.log(err));
  }, [])

  async function callBackendAPI(routePath) {
    const response = await fetch(routePath);
    const body = await response.json();

    if (response.status !== 200) {
      throw Error(body.message)
    }
    console.log(body.express)
    return body;
  }

  return (
    <div className="App">
      {{datas}}
    </div>
  )
}

export default App;

And the array content (seen from Chrome's developer tools):

(3) [{…}, {…}, {…}]
  0:
    prodDesc: "Prod1"
    prodID: "1"
    prodImage: "prod1.jpg"
    prodPrice: 350
    prodRebate: 100
    prodReviews: []
    prodStock: 31
    prodThumb: "prod1.jpg"
    __v: 0
    _id: "5eb04a6439eec26af4981541"
    __proto__: Object
  1: {prodID: "2", prodDesc: "Prod2", prodThumb: "prod2.jpg", prodImage: "prod2.jpg", prodPrice: 1500.79, …}
  2: {prodID: "3", prodDesc: "Prod3", prodThumb: "prod3.jpg", prodImage: "prod3.jpg", prodPrice: 280.79, …}
  length: 3
  __proto__: Array(0)

When I execute this code I get the error: Objects are not valid as a React child

So I tried to map data's content with a line like this:

{datas.map((item) => <p>{item}</p>)}

Executing this code with a map I get the error : Cannot read property 'map' of null.

Although datas contains an array, it seems React sees it as null.

Anybody understands this issue?

EDIT: I didn't realized that this question is about two different issues. Norbitrial's answer fixed the problem about synchronicity but I still get the error "Objects are not valid as a React child [...] use an array instead".

EDIT2: Solved the issue by forwarding the mapping to a REACT component with the following line:

{datas && datas.map(item => <ShopCart key={item.prodID} item={item} />)}
sevynos
  • 57
  • 1
  • 8
  • Does this answer your question? [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](https://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) – Heretic Monkey May 07 '20 at 19:30
  • Not related to your problem at hand, but you are doing your async function on the server incorrectly. It will still work, but your `await` isn't going to do anything since it's a function that has a callback function rather than one that returns a Promise. You can use `require('utils').promisify` to convert the function to a Promise-returning one: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_promisify_original – Jacob May 07 '20 at 19:30

3 Answers3

2

The problem is your backend API returns the result asynchronously so it might take few seconds while your state value is null.

The solution can be checking for null value first then using the .map() on your state called datas with && in your code.

Try as the following:

{datas && datas.map((item) => <p>{item}</p>)}

I hope this helps!

norbitrial
  • 14,716
  • 7
  • 32
  • 59
0

The code runs before datas is populated from your api, you need to first check to make sure it is populated like this.

{datas && datas.length && datas.map((item) => <p>{item}</p>)}
Shmili Breuer
  • 3,927
  • 2
  • 17
  • 26
0

The problem is that your component gets mounted before you receive the data.

What I would do(and most people) is to create a loading component that will show a spinner or something like that when the array is empty and after it gets filled it should render the component with the data.

But a quick solution to this is to do a conditional render


{datas.length && datas.map((item) => <p>{item}</p>)}
Suraj Auwal
  • 322
  • 3
  • 9