How to fix Error: object is not valid as React child

Viewed 5052

I am learning React and I was trying to develop a code to fetch random 10 numbers with corresponding interesting facts from an api "Numbers API".

the issue I am facing is that, when I run the code, an error appears

"Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead"

below I have attached the code:

const originalArray = new Array(10).fill(0);
const mappedArray = originalArray.map((n, index) =>
  Math.floor(Math.random() * 100)
);

console.log("mappedArray", mappedArray);
console.log("joined array string", mappedArray.join(","));

async function callApi(numbers) {
  const fetchResponse = await fetch(`http://numbersapi.com/${numbers}/math`);

  if (fetchResponse.ok) {
    const data = await fetchResponse.json();
    console.log("all good, heres the response", data);
  } else console.log("There was a problem");
}


const Number = async() => {
    const [add, setAdd] = React.useState([]); // <-- valid initial empty state

    React.useEffect(() => {
        callApi(mappedArray).
          then(values => setAdd(values));
      }, []); // <-- invoke on component mount
    
  <div className="container">
    {add.map((newlist, i) => (
      <div className="item" key={i}>
        {newlist}
      </div>
    ))}
  </div>
);

export default Number;

Can someone help me fix this issue? Thank you

3 Answers
  1. You need to maintain state. React is very particular with it's lifecycle. So here I have a data state, and a function - setData - that updates it.

  2. For React function components we use useEffect - essentially the old componentDidMount from class components - to load data when the component first mounts. We can then set the state with that data which we now know is an array of objects.

  3. Once the state updates the component gets re-rendered. You can then iterate over the Object.entries using the key for the key, and the value as the text source.

function Number() {

  // Initialise the state with an empty array
  const [data, setData] = useState([]);

  // Separate out the function that creates the number array
  function createNumbers() {
    const originalArray = new Array(10).fill(0);
    return originalArray.map(() => Math.floor(Math.random() * 100)).join('');
  }

  useEffect(() => {
    async function getNumbers() {
      try {
        const res = await fetch(`http://numbersapi.com/${createNumbers()}/math`);
        const data = await res.json();

        // Set the new component state using the data
        setData(data);
      } catch (err) {
        console.log(err);
      }
    }
    getNumbers();
  }, []);

  return (
    <div className="container">
      {Object.entries(data).map(([key, value]) => (
        <div className="item" key={key}>
          {value}
         </div>
       ))}
     </div>
   );

};

 export default Number;

That's because your add function is still return a Promise since it an async function

You will still need to await for add to finished, then use it:

Something like this:

const Number = async () => {
  const data = await add;
    <div className="container">
      {data.map((newlist, i) => (
        <div className="item" key={i}>
          {newlist}
        </div>
      ))}
    </div>;
};

callApi is async so it implicitly returns a Promise, which you save into add and attempt to render. This is the object that React is complaining about.

You will want to invoke this function within the React component lifecycle and save the result into state to be rendered.

const Number = () => {
  const [add, setAdd] = React.useState([]); // <-- valid initial empty state

  React.useEffect(() => {
    callApi(mappedArray).
      then(values => setAdd(values));
  }, []); // <-- invoke on component mount

  return (
    <div className="container">
      {add.map((newlist, i) => (
        <div className="item" key={i}>
          {newlist}
        </div>
      ))}
    </div>
  );
};

When you send a comma separated list of numbers to this numbersapi it returns a JSON object that isn't renderable as an array.

The response format will always be a JSON map from numbers to facts, of at most 100 numbers.

Example response to http://numbersapi.com/53,65,70/math

{
  "53": "53 is the 16thprime number.",
  "65": "65 is the 23rdsemiprime and the 3rd of the form (5.q)it is an octagonal number.",
  "70": "70 is the smallest weird number."
}

It seems you want to either save the values into state:

React.useEffect(() => {
  callApi(mappedArray).
    then(result => setAdd(Object.values(result)));
}, []);

Or you can get the array of values when rendering:

return (
  <div className="container">
    {Object.values(add).map((newlist, i) => (
      <div className="item" key={i}>
        {newlist}
      </div>
    ))}
  </div>
);

If you want both the key and the value, then use Object.entries to get an array of array of key value pairs.

Example:

[
  ['53', '53 is the 16thprime number.'],
  ['65', '65 is the 23rdsemiprime and the 3rd of the form (5.q)it is an octagonal number.'],
  ['70', '70 is the smallest weird number.'],
]
return (
  <div className="container">
    {Object.entries(add).map(([key, value], i) => (
      <div className="item" key={key}>
        {key}: {value}
      </div>
    ))}
  </div>
);
Related