Why state doesn't keep all the data in Array state in hook when it updated in foreach?

Viewed 72

I want to update my Array state in hook by foreach but it just keeps the last one. I also used the empty array for useEffect to stop re-render each time by updating state.

import './App.css';
import Card from "./Card"
import Books from "./Books"
import { useEffect, useState } from 'react';
import axios from "axios"
import BooksInfo from './Books';
function App() {
  const [bookInfo,setBookInfo] = useState([])
  useEffect( ()=>{
    let bname = ""
    let bauthor = ""
    let bcover = 0
    async function getInfo(){
        let results = Promise.all(BooksInfo.map(book=>{
          let result = axios.get(`https://openlibrary.org/search.json?q=${book}`)
          return result
        }))
        ;(await results).forEach(result=>{
          console.log(result)
            bname= result.data.docs[0].title_suggest
            bauthor= result.data.docs[0].author_name[0]
            bcover= result.data.docs[0].cover_i
            setBookInfo([...bookInfo , { name:bname , author:bauthor  , cover:bcover }]);
            console.log(bookInfo)
        })
    }

    getInfo()
  },[])  

Here is the additional code for the one of the comments: This code help me to understand how state and useEffect work.

function App(){
  console.log("App Rendered!");

  const [counter, setCounter] = useState(0);
  const [test, setTest] = useState(0)
  const [run,setRun] = useState(0)
  useEffect(()=>{
    console.log("inside useEffect")
    setCounter(counter => counter + 1);
    setTest(test => test +counter)

  },[run])
  const count= ()=>{
    console.log("test")
    setRun(run => run + 2)
  }
  return (
    <div>
      <h1>Counter: {counter}</h1>
      <button
        onClick={count}
      >
        Increment
      </button>
    </div>
  );
}
export default App;
2 Answers

Try this, setBookInfo is an async call, When you use it inside the loop it won't take the latest value for the next iteration. Instead, it takes the last value(initial). that's why it shows the last value.

import "./App.css";
import Card from "./Card";
import Books from "./Books";
import { useEffect, useState } from "react";
import axios from "axios";
import BooksInfo from "./Books";
function App() {
  const [bookInfo, setBookInfo] = useState([]);
  useEffect(() => {
    async function getInfo() {
      let results = await Promise.all(
        BooksInfo.map((book) => {
          let result = axios.get(
            `https://openlibrary.org/search.json?q=${book}`
          );
          return result;
        })
      );

      const finalResult = results.map((result) => {
        let bname = "";
        let bauthor = "";
        let bcover = 0;
        console.log(result);
        bname = result.data.docs[0].title_suggest;
        bauthor = result.data.docs[0].author_name[0];
        bcover = result.data.docs[0].cover_i;
        return { name: bname, author: bauthor, cover: bcover };
      });
      setBookInfo([...bookInfo, ...finalResult]);
    }
    getInfo();
  }, []);
}

The setBookInfo function is async, so there is nothing guarantee that after a each iteration, the data of bookInfo is updated with new inserted data. The map function is more suitable for your situation

const newBookInfor = (await results).map((result) => {
    let bname = "";
    let bauthor = "";
    let bcover = 0;
    bname = result.data.docs[0].title_suggest;
    bauthor = result.data.docs[0].author_name[0];
    bcover = result.data.docs[0].cover_i;
    return { name: bname, author: bauthor, cover: bcover };
});
setBookInfo(newBookInfor);
Related