clattenburg cake
clattenburg cake

Reputation: 1222

Struggling With JS Promises in React

I'm struggling with the concept of Promise in JavaScript. I'm writing a React app that makes a GET call out to a separate API service in Java, and I want to store its state in a useState() hook. So here's my fetch code:

const ratingsUrl = "%URL%";
const base64 = require("base-64");
const login = "login";
const password = "password";

function fetchRatings() {
  return fetch(ratingsUrl, {
    method: "GET",
    headers: new Headers({
      Authorization: "Basic " + base64.encode(login + ":" + password),
    }),
  })
    .then((response) => response.json())
    .catch(handleError);
}

And now I'm trying to store its state in a hook in my page component:

function DisplayPage(){
  const [ratings, setRatings] = useState(fetchRatings());

.
.
.
}

Now, the data returns but it's in a Promise, hence causing errors down the line:

Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Array(20)

What I need to do is to initialise the data in a hook and return it in a Table so I can map through it. However, whenever I try to do something like

ratings.map()

I get a TypeError in the console saying ratings.Map is not a function.

I'm aware that the fetch library returns data asynchronously, but all I really want is for the PromiseResult to be stored in a useState() hook so I can perform operations on it further down the line.

Upvotes: 0

Views: 2531

Answers (4)

ray
ray

Reputation: 27285

Because the fetch runs asynchronously, you're not going to be able to initialize your state using the immediate result of invoking fetchRatings.

There are, fortunately, a couple of fairly straightforward ways to handle this. You can initialize your state with an empty value and then update it once fetchResults resolves:

function DisplayPage() {
  // initially ratings will be undefined
  const [ratings, setRatings] = useState();

  // update state when fetchResults comes back
  fetchResults().then(response => setRatings(response));

  // ...

The above example omits this in the interest of readability, but you'd generally do this via useEffect so it runs when your component mounts or when relevant inputs (usually props, known as dependencies for the effect) change:

function DisplayPage() {
  const [ratings, setRatings] = useState();

  useEffect(() => {
    fetchResults().then(response => setRatings(response));
  }, []) // empty dependencies array will cause the effect to run only once, on mount

  // ...

Upvotes: 0

Yannick Vermeulen
Yannick Vermeulen

Reputation: 148

I would advice using the useEffect hook to set initial state.(similar to componentDidMount)

So if the response you expect is for example an array.

const [ratings, setRatings] = useState([]);

Then in the useEffect hook, update state when you get a response from your fetch request. That way you can prevent errors if you for example map over ratings in your DOM somewhere before the request is finished.

useEffect(){
    fetch(ratingsUrl, {
        method: "GET",
        headers: new Headers({
            Authorization: "Basic " + base64.encode(login + ":" + password),
        }),
    })
    .then((response) => {
         response.json()
    })
    .then(res => setRatings(res))
    .catch(handleError);

Upvotes: 0

Arjun Biju
Arjun Biju

Reputation: 73

How About this ,

const [ratings, setRatings] =  useState();
useEffect(()=>{
         fetch(ratingsUrl, {
method: "GET",
headers: new Headers({
  Authorization: "Basic " + base64.encode(login + ":" + password),
})}).then((response) => {let res = response.json();
    setRatings(res)
      })
.catch(handleError);
 },[])

Upvotes: 0

Evert
Evert

Reputation: 99861

async methods return promises. If you directly set the result of a promise in your setRatings state variable, you will get a promise.

Typically this would be rewritten something like this:

function DisplayPage(){
  const [ratings, setRatings] = useState(null);

  useEffect(() => {

    fetchRatings
      .then(result => setRatings(result))
      .catch(err => console.error(err));

  }, []);
 
  if (ratings === null) return <div>loading...</div>;
  
  /* .. do your thing .. */

}

Upvotes: 2

Related Questions