0

Scenario -

Suppose, there is an API which gives a list of books.

[ { id:1,
name: "A",
author: "https://author/1"
},
{ id:2,
name: "B",
author: "https://author/10"
},
{ id:3,
name: "A",
author: "https://author/3"
},
{ id:4,
name: "A",
author: "https://author/1"
}
...
]

Now, I need to call the author API to get the author details, but I need to avoid calling those APIs which has already been called.

I tried doing it by keeping a state object and updating the object whenever a new api is called. If the API URL already has an entry in the object, that call is not repeated. However, I suspect because of async nature of setState, the update to the state Object are clubbed and in the subsequent iteration, when I check the object to find the previous entry, it doesn't reflect.

...
const[authorDetail, setAuthorDetail] = useState({});

useEffect(() => {

for(let i=0;i<books.length;i++)
{
  if(!authorDetail[books[i].author]) {
     // make API call to fetch author detail
     authorDetail[books[i].author] = "called"
     setState({...authorDetail});
}
});

How can I optimise the API call for this case. For example, if I have 7 Harry Potter books, I would like to make only one call to fetch J.K. Rowling's data.

Rohan Agarwal
  • 2,441
  • 2
  • 18
  • 35
  • are you saying you need the previous API call to finish before making the next one? are you able to use an async for loop? lots of good answers about that here https://stackoverflow.com/questions/24586110/resolve-promises-one-after-another-i-e-in-sequence – azium Nov 24 '21 at 20:21
  • No @azium. It's fine if calls are made in parallel. I need to avoid sending API request for already request API. For example, if I have 7 Harry Potter books, I would like to make only one call to fetch J.K. Rowling's data. – Rohan Agarwal Nov 24 '21 at 20:28
  • what about something like `Promise.all(books.map(x => if (already there) promise.resolve() else return api call))` – azium Nov 24 '21 at 20:30
  • If am not wrong, the "already logic" is something which is tricky. How do you check if an API call has already been made. If you keep a track of it in an state object, in the next iteration the object is not updated. – Rohan Agarwal Nov 24 '21 at 20:39
  • the point of using promise.all is to do all of the operations before calling setState. the point of tracking stuff in react state is to force the app re-render which you don't need until the calls are done – azium Nov 24 '21 at 21:00

1 Answers1

1

I believe the problem here is that your effect is maintaining a reference to the old state object. I think the easiest way to solve it will be by using a ref object.

const authorDetail = useRef({});

useEffect(() => {
  for (let i = 0;i < books.length; i++) {
    if (!authorDetail.current[books[i].author]) {
      // make API call to fetch author detail
      authorDetail.current[books[i].author] = "called"
    }
  }
}, [books]); // note the dependencies here!

books is a dependency for the effect, but you could also provide empty square brackets ([]) which will force it to only run when the component mounts.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Jeremy Klukan
  • 228
  • 1
  • 4