Returning a Promise value from fetch

Returning a Promise value from fetch
0

I’m struggling getting fetch data into my react component after fetching it. The console shows my data but I’m not clear on what to do after.

Here is my fetch call (URL hidden, contains sensitive API)

 function apiGetAll () {
   // console.log("Fetching stuff")
   fetch(URL)
     .then(function(response) {
        console.log (response.json())
   })
  }

And I call it here, but when I try to log the variable ‘api’, I don’t get undefined, and not teh promise value

  componentDidMount () {
   console.log("Component did mount")
   const api = apiGetAll() 
  }

This is my response logged after the fetch call, which shows all my data.

How do I get that data into my React component?

I imagine you want that data to be rendered somewhere on the page.

A common pattern would be to store that as state in the class, so that you can update the UI accordingly:

A basic idea could be:

class A extends React.Component {
  state = { data: [] }


  componentDidMount() {
    // fetch data and update state
    fetch(api).then(response => this.setState({ data: response.data)).catch()
 }

render() {
  return (
    <div>
     // if  !this.state.data.lenght render a loading UI so the user knows we are fetching something

      // otherwise this.state.data.map(el => <SomeMarkupWithArray />
</div>
)
}
}

Hope it helps :slight_smile:

thanks, i understand the pattern, but i’m unable to get the data into my code, it’s always “undefined”, even though I can console.log the promise and see the data.

How are you actually updating the data?

Are you sure you are not running in a simple case of asynchronous data?
As you know fetch is async, thus the JS engine won’t wait for it to return some value before moving to the next statements.

   fetch(URL)
     .then(function(response) {
       // here the promise is resolved so you can see the data
      // so if you want to perform some changes here's the place
        console.log (response.json())
   })

   // the JS enginw won't wait the previous fetch to resolve, but will evaluate this statement
  // and the data is still not came back so it's undefined
   console.log(response.json)

You’re not returning the promise. I was going to mention this when I figured out that other bug, but you should switch from promises to async /await.

Promises are better than callbacks, but the logic flow is just as hard to understand. Async /await Let’s you write async code that looks synchronous.

Also, you don’t get a result from a promise. You get a promise of a result. This means you will get undefined as the return value of apiGetAll. So const api will always equal undefined.

If you still want to use promises, that’s fine. But async/await would have made it clearer that you have to set state in the the callback because the value doesn’t exist until the promise returns.

Blockquote

1 Like

Here’s an example using async/await

async function apiGetAll () {
   try {
     const resp = await fetch(URL)
     console.log(resp)
     return resp
   } catch (err) {
        console.log(err)
     }
}

This, at least to me, makes it clear that I don’t have a value for resp until the promise resolves. Plus, you can await multiple promises without nested promise chains

async function apiGetAll () {
   try {
     const resp = await fetch(URL)
     const newResp = await somePromise(resp)
     
     return newResp
   } catch (err) {
// all errors will be captured here for anything in the try block
        console.log(err)
     }
}

3 Likes

EDITED TO ADD: I had several different attempts at updating state or returning a varaible and none worked so I stripped it down to just the one “then” statement.

this is what I had originally (sort of) tried. I keep including a console.log somewhere just to convince myself I have the data somehow. With this version below I get an error that setState is not a function

 componentDidMount () {
   console.log("Component did mount")
   fetch(URL)
     .then(function(response) {
       this.setState({
       resources: response.json()})  
   })
    .then(function(response){
      console.log( response.json())
     
   })

At this point it seems like callbacks are easier, lol. I AM reading the documentation, it’s just not real clear withotu lots of examples for me. I really appreciate all of youhelping me out with this.

This little bit of code is an attempt to create a minimal solution to help me track down the issues with the emquick app, and I’m running into problems here as well.

I’m halfway done with the backend projects, all but the roguelike of my dataviz apps, andhave the front end cert…there is so much to learn and master, it’s mind boggling.

The emquick is my first full stack with React and I refuse to give up until I get things working.

I hope I can repay the favors to you or to other campers

Remember that the response.json() also returns a promise. You would need to either await or then the json parsing too.

async/await

var response = await fetch(url);
var data = await response.json();
console.log(data);

or with promises

fetch(url)
    .then(response => response.json())
    .then(json => console.log(json)); 

EDIT: The reason you are getting setState is not a function is because this is currently bound to your then callback. You need to either bind the callback to the component or use ES6 function.

componentDidMount () {
   console.log("Component did mount")
   fetch(URL)
     .then(response => response.json())
     .then(json=>  {
       this.setState({
       resources: json})  
   })
}

or alias “this” before you fetch

componentDidMount () {
    var self = this;
    fetch(URL)
        .then(function(response){ 
            return response.json(); 
        })
        .then(function(json){ 
            self.setState({resources: json}); 
        });
}
4 Likes

Ahh…so the promise is always a double statement? and forgive me…I do write arrow functions in react most o fthe time, but just to be 100% clear, in your sample, what follows the => is what is returned from the function?

Thats correct. response => response.json()
is the same as function(response) { return response.json() }

Is promises always a double statement? No, but response.json() is a also a promise and the reason this solution works with two then callbacks is because promises a chainable. Meaning that if you return a promise inside a then callback it will be resolved in the next then callback. First we resolve the actual network request, then we resolve the json parsing (which is also an async operation like fetch itself). If that makes sense.

2 Likes

So let me give you more examples :slight_smile:. It really will remove alot of the overhead that promises have with working on results. The documentation can be sparse and dry. It took me a few months to truly grok it, but much of what I read wasn’t even necessary. So I’ll try to give you a mental model to work with.

First some definitions. An async has 3 components

  1. async keyword
    • this tells the js engine you will be working asynchronously
  2. await keyword
    • this tells the engine where to pause execution
  3. a try catch block
    • this helps catch any errors that pop up.

Number 2 is the most important. Think of it as actually pausing execution. This is not technically true, but for a mental model is more than enough. IOW, you can think of the await declaration as pausing the entire program.

That part is important. It acts like any other code does, it “blocks” the execution. This is to make following the logic easier. So if you look at it like that, then what we have is:

  1. a fetch promise
  2. a response.json promise
// here we tell the engine we're working async
async function apiGetAll () {

// here we tell the engine to PAUSE EXECUTION and wait for a response
// once it has a reponse, assign it to the variable 'resp' and continue
     const resp = await fetch(URL)

// this code will NOT run until 'resp' has been assigned
// and then the program will PAUSE until it gets a reponse
     const data = await response.json()

// this code only runs when data is assigned.
     return data
   } catch (err) {
        console.log(err)
     }
}

Let me know if that example clears it up for you. If not, I’ll try again.

and

This is why I wholeheartedly believe you’ll have a less frustrating time using async/await once you grasp the mental model. Promise logic is still complex when trying to figure out where things are being returned from. Whereas async/await you can think of it as “blocking” (synchronous code). I’ve stopped using promises altogether.

This determination is why I’m willing to work with you step by step :smile:

I noticed that you don’t have a development environment. And I think it’s what’s causing you most the issues. So I copied over some boilerplate that I have been collecting to set you up with one. Don’t feel discouraged because it took me about 6 months to figure out how to set up a dev environment. And it turned out to be mostly boilerplate anyways. Hopefully once I find your bug you’ll find it useful.

Here’s what should help you the most

  1. a working local instance of mongodb
  2. a working local instance of nosqlclient to browse your database
  3. debug package from npm to help you feel more confident you won’t break your app unknowingly.
  4. a reproducible clean working environment, again so you can feel free breaking things.
3 Likes

I do have mongo lically, i started using Mlab so I could deploy the app and start using it and showing it off.

I used to have a db browser, have not used it recently.

I have never used debug package just the browser console tools and whatever the ide offers (webstorm)

Much of what you just wrote abou abdev environment is Greek to me but it sounds exciting! I know I’m taking shortcuts that I’d learn if I were in a professional environment. But as a 49 year old physician, Im never going to be working as an employee in a dev firm. I want to learn enough to build tools and possibly monetize them, and also juts the challenge of learning to code is extremely satisfying. There are only so many lacerations you can see before it stops being super interesting. :slight_smile:

The debug package is great because you can filter for any statements you want. For instance, if you only wanted to see console statements from your server, you’d set your env as

DEBUG=server:*
and if you only cared about statements above a certain debug level

DEBUG_LEVL=warn

this is really useful since you don’t bombard the user with console statements in production, but you can leave them literally everywhere for future reference. Here it is filtered and unfiltered (with sockjs statements).

In that case you should be able to use the express/mongo/debug boilerplate I created. To be frank, you don’t have to know all this. Once it’s set up you can just start to get to work on the fun stuff. Think of it like using webpack.

Your server logic was sound, so it was easy to make a boilerplate out of it.

I assumed that since you didn’t have any scripts dealing with a local db, that you didn’t have one. I added one anyway using docker. I personally prefer docker because I can destroy and rebuild containers with certainty. And sometimes I corrupt my database when messing around. Fail fast and hard as they say. lol

I feel your pain. Learning to set up a dev environment was a torturous half year for me. And in the end I just used boilerplates anyways. smh.

But I firmly believe, after much suffering before setting one up, that it only causes more pain to not have one. So hopefully I can get you set up to avoid all those lacerations (of which there are many in javascript). Especially since you already know how to launch server properly.

Essentially the setup goes like this:

  1. set your environment variables
  2. boot up the client server in one terminal
  3. in a separate terminal start the server
  4. if you set the DEBUG env var, it will start debugging.
  5. the script then boots up your docker containers
    • mongodb
    • nosqlclient
  6. it will bootstrap and load all your models in the models directory
    • you can then access them throughout your codebase as mongoose.models[nameOfModel]
  7. if you set any models to seed in the env var, it will seed the db

I’m going to abstract alot of this away so that you don’t even have to look at it, except for setting up a few variables. This way you can focus on building software, instead of fighting against it like I did.

As an example, here’s what you’ll need to adjust to set up express/mongo/debug if you use this boilerplate:

/**
 * Configuration variables
 */
const isDev = process.env.NODE_ENV === 'development'
const staticFilesProd = path.resolve(__dirname, 'build')
const staticFilesDev = 'public'
const staticFiles = isDev ? staticFilesDev : staticFilesProd
const port = process.env.BACKEND_PORT || 8080

// list of middleware and routes in order of preference
const middleware = [
  morgan('dev'),
  cors(),
  bodyParser.json(),
  bodyParser.urlencoded({ extended: true }),
  // Priority serve any static files.
  express.static(staticFiles),
  // set resources routes
  ['/api/resources/', resourceRoutes],
]

// placeholder variable for a function to run cleanup tasks on server exit.
let cleanUp


// env vars
# Do not use quotes around values. They will be automatically formatted
MLAB_URI=

# All create-react-app variables need to be preprended with REACT_APP
REACT_APP_PORT=3000
REACT_APP_DEBUG=*
REACT_APP_DEBUG_LEVEL=

# Explicitly set server port during dev
BACKEND_PORT=4000

# Database variables
DB_URL=mongodb://127.0.0.1:27017/database
# only needed in dev
DB_NAME=emquick-mongo

# space or comma separated filenames of models to seed.
SEED=

# set namespace to show debugging for. namespaces are file specific
# wildcards are acceptable (*)
# we can also exclude 3rd party debug statements
DEBUG=* -express:*
# set lowest level to output info for. anything less than the
# level will not be outputted
# 0-trace 1-debug 2-log 3-info 4-warn 5-error
DEBUG_LEVEL=trace

I should hopefully be done with the boilerplate and troubleshooting your bug by end of this weekend. Hopefully you’ll find this useful somehow.

1 Like

Oh I’m sure I will make very good use out of it! I’ve mentioned it before, but I really appreciate what you are doing to help me out.

1 Like

Just to follow up on the original post I made here, this code worked for me. I don’t know why it seemed so hard. (OK, well I do, but thanks to you guys I did finally get it working!)

Code: (URL string remains hidden due to private API)

class App extends React.Component {
  constructor() {
    super()
    this.state = {
      resources: [{"name": "fig"}, {"name": "Donna"}]
    }
  }


componentDidMount () {
   fetch(URL)
     .then(response => response.json())
    .then (json => 
          this.setState({
          resources: json
     }))
  }  
  
  render() { 
    const resArr = this.state.resources.map((r, i)=> {
      return (<li key={i}>
        {r.name}
      </li>
      )
    })  
    return (
      <div>
        <h1>Welcome to my App!</h1>
        <ul>
        {resArr}
        </ul>
    </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('app'))

And the output is here:

1 Like

Just came here to say thanks. I was having this problem for like 3 hours.

2 Likes

Thanks a lot! The solutions also works for me!

1 Like

Hello JM, I have used your code to my script but async wait is not working . It returns promise still. here is my code.
https://lygfq.csb.app/

but when i simply use fetch and then it works fine.

I can’t view the code using the link you provided. It only shows me the finished app. Are you hosting it on codepen or codesandbox?