1

I have a problem solving this react.js

loadFromServer(pageSize) {

        fetch('http://localhost:8080/api/employees')
        .then(response => {
            return fetch('http://localhost:8080/api/profile/employees',
            {
                headers: new Headers({
                'Accept': 'application/schema+json'
              })
            }).then(schema => {
                this.scheme =  schema;
                return response.json();
            }   
            )

       })
        .then(response =>             
            this.setState(
                {
                employees: response._embedded.employees,
                attributes: Object.keys(this.scheme.json().properties),
                pageSize: pageSize,
                links: response._links}                      
            )
        );          
    }

at this part

attributes: Object.keys(this.scheme.json().properties),

always return (in promise) TypeError: Cannot convert undefined or null to object.

If I put console.log(this.scheme.json()) I can see the Promise but, why inside setState I get null object?

4 Answers 4

2

There are a few issues here:

  • The main one is that this.schema.json() returns a promise (as you know from your console.log). Promises don't have a properties property, so you're passing undefined to Object.keys, which then gives you that error.
  • You're also not checking for errors from fetch, in two different ways: You're not checking .ok (which is such a common error I've posted about it on my anemic little blog), and you're not checking for promise rejection.

You're also doing some unnecessary promise nesting and could be overlapping your fetch calls more.

First, since it seems you often fetch JSON, I'd suggest giving yourself a utility function for it:

function fetchJSON(...args) {
    return fetch(...args)
        .then(response => {
            if (!response.ok) {
                throw new Error('HTTP error ' + response.status);
            }
            return response.json();
        });
}

Notice the .ok check.

Then, also in the "break the problem into smaller pieces" category, I'd have a fetchSchema function:

function fetchSchema(url) {
    return fetchJSON(url, {
        headers: new Headers({
            'Accept': 'application/schema+json'
        })
    });
}

Then, loadFromServer can use Promise.all and destructuring to run the operations in parallel:

// (I assume this is in a `class` or object initializer, as it doesn't have `function` in front of it)
loadFromServer(pageSize) {
    Promise.all(
        fetchJSON('http://localhost:8080/api/employees'),
        fetchSchema('http://localhost:8080/api/profile/employees')
    )
    .then(([empResponse, schema]) => {
        this.schema = schema;
        this.setState({
            employees: empResponse._embedded.employees,
            attributes: Object.keys(schema.properties),
            pageSize: pageSize,
            links: empResponse._links
        })
    )
    .catch(error => {
        // Do something with the error
    });
}

Note the .catch, since you're not returning the promise from loadFromServer. (If you want to buck errors up the chain, add return in front of Promise.all and move the .catch to calling code.)


Side note: Your code used

this.scheme =  schema;

Note that the property on the left is scheme (with a final e) but the variable is schema (with a final a). I think you meant schema and so I've included that change in the above, but if the property is really supposed to be this.scheme, you'll want to adjust that. Or if you don't need that property for anything other than the code in loadFromServer, remove that line entirely.

Sign up to request clarification or add additional context in comments.

4 Comments

Why do we need this.schema = schema; any more? :)
@ArupRakshit: I assume this is a method and that having that property is important. Naturally, if the OP doesn't need that property, they should remove that.
I agree. But I am assuming OP stored it to this to access it later, but that fundamental was wrong which the OP realised from the error message. So, it is probably no more needed.. Anyway it is OP's call. I just tried to make a note here.
yes.. its important using catcherror if something happen while fetching..thanks
2

I think you should use Promise.all to run the two requests in parrallel and then retrieve the two responses (by the way response.json() returns a Promise, that's why you have an error in your code) :

loadFromServer(pageSize) {
    Promise.all([
        fetch('http://localhost:8080/api/employees')
        .then(response => {
           if (!response.ok) throw Error(response.statusText);
           return response.json();
        ),
        fetch('http://localhost:8080/api/profile/employees')
        .then(response => {
           if (!response.ok) throw Error(response.statusText);
           return response.json();
        ),
    ]).then(responses => {
        this.setState({
          employees: responses[0]._embedded.employees,
          attributes: Object.keys(responses[1].properties),
          pageSize: pageSize,
          links: responses[0]._links
        })
    }).catch(error => {...})        
}

3 Comments

yups .. using promise.all is elegant way to handle pararel fetching...thank you so much.
It also ignores errors from fetch, which will eventually turn into "Unhandled promise rejection"s.
yep btw I am not a big fan of fetch api, I prefer using axios which is in my opinion easier to use
1

I think you need something like:

loadFromServer(pageSize) {
  fetch('http://localhost:8080/api/employees')
    .then(response => {
      return fetch('http://localhost:8080/api/profile/employees', {
        headers: new Headers({
          'Accept': 'application/schema+json'
        })
      }).then(schema => {
        schema.json().then(data => {
          this.scheme = data
        })
      });
      return response.json();
    })
    .then(response =>
      this.setState({
        employees: response._embedded.employees,
        attributes: Object.keys(this.scheme.properties),
        pageSize: pageSize,
        links: response._links
      })
    );
}

Comments

1

Response json() method in Fetch API returns a promise. For this reason fetch requests should be consistently chained with .then(response => response.json()) to get a plain object.

Flattening promises may result in more reliable control flow. Since responses from both requests are used, this would require to either nest then callbacks or passing another response through then chain. async may be useful because it conveniently solves flattening problem:

async loadFromServer(pageSize) {
    const employeesResponse = await fetch('http://localhost:8080/api/employees', {
      headers: new Headers({ 'Accept': 'application/schema+json' })
    });
     const employees = await employeesResponse.json();

    const schemeResponse = await fetch('http://localhost:8080/api/profile/employees', {
      headers: new Headers({ 'Accept': 'application/schema+json' })
    });
    const scheme = await schemeResponse.json();

    this.setState({
        employees: employees._embedded.employees,
        attributes: Object.keys(scheme.properties),
        pageSize: pageSize,
        links: response._links
    });
}

Since requests don't depend on each other, they could be performed in parallel with Promise.all.

async loadFromServer(pageSize) {
    const employeesPromise = fetch('http://localhost:8080/api/employees', {
      headers: new Headers({ 'Accept': 'application/schema+json' })
    })
    .then(res => res.json());

    const schemePromise = fetch('http://localhost:8080/api/profile/employees', {
      headers: new Headers({ 'Accept': 'application/schema+json' })
    })
    .then(res => res.json());

    const [employees, scheme] = await Promise.all([employeesPromise, schemePromise]);

    this.setState({
        employees: employees._embedded.employees,
        attributes: Object.keys(scheme.properties),
        pageSize: pageSize,
        links: response._links
    });
}

2 Comments

Then how do I get response second fetch --> schema to put in setState()?
With ES6 this this usually involves either nested then, or passing responses through thens with spread/destructuring syntax, or assigning them to temp variables in parent scope. This becomes much simpler with async Updated the answer. Hope this helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.