0

Fetch works, but only when using async behaviour.

I'm re-building an existing property booking website. It was created with Laravel and used a self-built API. I am re-building with React with Next JS (new to me). All of the property information comes from a remote API (not under my control).

/index.js Once you hit the browse page, you can browse properties. Then you choose a property. The data for the selected property and an initial calendar for this property comes from the API using this pattern:

export const getServerSideProps = async (context) => {

   // Property detail
    const res = await fetch(`https://remote_api_address_property_info_endpoint`, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': auth,
        },
    })

    const property = await res.json()

    const res = await fetch(`https://remote_api_address_calendar_info_endpoint`, {
         method: 'GET',
         headers: {
             'Content-Type': 'application/json',
             'Authorization': auth
         },
    })

    const calendar = await res.json()


    return {
        props: {
            property,
            calendar
        }
    }

}

The property and calendar data are fetched and I send the data as props to the 'View' page. This page renders out all of the data, including the calendar. The problem starts when I choose 'next' on the calendar, on this page to show the following (or previous) month. There's an inbuilt event hook in the calendar code (an npm calendar plugin) for when 'next' is clicked and I use that to run a function which attempts to bring in the following month's calendar data. I have used various versions of the code above to pull that data, feeding in the current month etc. to bring in the new data, but to no avail. For some reason, fetch now seems to pre-flight the request to the API but comes back with an error of 'failed to fetch' from the server. That's the only error message I can elicit from the server.

I have tried using async (and not). I have tried moving the request to its own apihelper.js file and including it with and without async, each time trying to return some results. I can use the generated url and auth string and get the data every time from Postman but not in this secondary situation (it still works on the inde.js page). Can anyone help with this? I feel it's something missing from the headers that the API doesn't like but after a few days, I'm all out of ideas. If I have to, I will try to contact the company and see if they'll check my incoming requests and tell me why they are being rejected but they're in a different time-zone and I'm not sure how helpful they'll be with this type of request from a programmer. Anyhow, any ideas I could try greatly appreciated.

This is the current iteration of the function. It has been through many changes including using async and not but this is the essence of it:

The calendar (using 'react-calendar') for some context:

<div className={styles.reactCalendar}>
    <CalendarBox
        className={styles.reactCalendarBox}
        onChange={onChange}
        selectRange="true"
        returnValue="range"
        minDate={new Date()}
        tileDisabled={tileDisabled}
        onActiveStartDateChange={onDateChange}
/>

The function called from the calendar 'next' link:

const onDateChange = async (startDateInward) => {
        const result = await fetch(`https://api_endpoint_with_date_from_startDateInward`, {
            method: 'GET',
            headers: {
                "Content-Type" : "application/json",
                'Authorization': authorizationString
            }
        }).then(response => {
            if(response.status >= 400) {
                throw new Error("Server responds with error")
            }
        }).then(calendarInfo => {
            console.log('got a result')
        },
        err => {
            console.log('Error is: ', err.message)
        })
        return result
}

I noticed a difference in the user-agent from successful requests an non-successful. I include it here in case it adds a clue:

Request headers from Postman:

Succesful request: user-agent: "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"

Non-successful request: user-agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"

Wittner
  • 583
  • 5
  • 21

2 Answers2

0

Did you not encounter error when you declare const res twice?

export const getServerSideProps = async (context) => {

   // Property detail
    const property = await fetch(`https://remote_api_address_property_info_endpoint`, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': auth,
        },
    }).then(res => res.json())


    const calendar = await fetch(`https://remote_api_address_calendar_info_endpoint`, {
         method: 'GET',
         headers: {
             'Content-Type': 'application/json',
             'Authorization': auth
         },
    }).then( res => res.json())

    return {
        props: {
            property,
            calendar
        }
    }

}

Update: CORS issue

You are facing cors issue likely. getServerSideProps is server side so you don't get cors issue. Fetching from your component (web browser) will face cors issue since it's different domain - npmjs.com/package/cors

Someone Special
  • 12,479
  • 7
  • 45
  • 76
  • Thanks Someone Special. No. No error. I did notice that at some stage and at least one version was created to ensure no clash of const names... to no avail – Wittner May 07 '21 at 10:34
  • That is a very astute observation. I've built and used so many APIs in my time, almost exclusively using PHP and cURL. Never occured to me that the two requests were different in that respect. Thank you. Not sure how to proceed because of that, but it's pointed me in a good direction... – Wittner May 07 '21 at 10:57
  • what is your backend? – Someone Special May 07 '21 at 11:01
  • I'm working on a Linux machine locally with React via Next JS. Not sure what the Next server is running (I run it via a script in package.json 'next dev'), some sort of local server with auto-refresh on changes. The backend of the API is a commercial API which I don't have any access to directly. After your CORS comment I published my test to heroku and ran it from there thinking that that might work better as it's server side but no joy yet – Wittner May 07 '21 at 11:41
  • You're right that the problem lies with where the api request originates. When I check the network tab, the requests which work look like requests to my local dev server. I can even copy/paste then into the browser and I get the results, but when I check the broken calls, they are trying to go directly to the remote api. I just found this error on those requests: "Cross origin resource sharing error:preflightmissingalloworiginheader". Although I can still get users from the github or typicode test api by just changing the url. Beginning to wonder if this is something on the api side... – Wittner May 07 '21 at 12:29
  • I think I need to build a 'CORS proxy server' to fix this. Unless the API people are prepared to set up an access-control header. Just found this if you're interested. If it's the answer to my problem I'll send people there: https://stackoverflow.com/questions/43871637/no-access-control-allow-origin-header-is-present-on-the-requested-resource-whe/43881141#43881141 – Wittner May 07 '21 at 12:43
  • Since you are using NextJS, you can create a `/api/` folder in `/pages/`, then you call the commercial API, with your own API. Your front end then can connect to your own custom api to retrieve the data without CORS issue, if you understand what I mean. The codes will be very similar to your serverSideProps, fetching and returning to your client. – Someone Special May 07 '21 at 13:13
  • That's an interesting approach too. Just got the proxy server working here today but would be preferable to have everything under my own control. I'll definitely give that a try... thanks – Wittner May 08 '21 at 13:27
0

Sending a fetch request from within a script sends a pre-flight request which was getting denied from the remote api server because it sees the request from directly in a browser and not from another server.

The only way I could fix this was to set up a proxy server to make the request. This worked and is my working method going forward.

I cloned this repo and published it to heroku and I send the queries through it.

CORS-anywhere Git repo

Simple to do. Here is a link to the install procedure:

How to install cors-anywhere

Wittner
  • 583
  • 5
  • 21