0

I need to request data from my REST server to populate my UI (frontend). In doing so, I need to request some data from my and other servers. One such request is to get a list of states (provinces), process each one and add them to a select HTML component. I use fetch() and .json() amongst other tools to do this.

Problem:

In calling my REST server for json data, I receive the following data (taken from Chrome console):

{provinces:[Eastern Cape,Mpumalanga,Western Cape,Gauteng,KwaZulu Natal,North West,Northern Cape,Free 
State,Limpopo]}

I intend to add each of these as an option to a select. While attempting to acquire the value for the provinces key, I get undefined.

I am making this call using:

fetch("http://localhost:3443/app/location/provinces").then(e => e.json()).then(e => console.log(e.provinces));

Further, since I can directly refer to json keys using the [] operator, I attempt this using

fetch("http://localhost:3443/app/location/provinces").then(e => e.json()).then(e => console.log(e['provinces']));

which as you may have guessed aswel, also returns undefined.

For the record, the full Chrome Console output is

Promise {<pending>}
undefined

Looking over some SO examples, I believe my call(s) may be correct, this one, and this one, and this one all which confirm its validity.

What else have I tried:

This SO post and this one suggested to use the json data response inside of the same then() call e.g.

fetch("http://localhost:3443/app/location/provinces").then(e => {
    e.json().then(s => {
        console.log(s['provinces']);
 });
});

and

fetch("http://localhost:3443/app/location/provinces").then(e => {
    e.json().then(s => {
        console.log(s.provinces);
 });
});

both which return:

Promise {<pending>}
undefined

What am I missing / doing wrong?


Update

Screenshot of Chrome console in order of commands listed above:

enter image description here

Resource file za-province-city.json

NodeJS express code:

const express = require('express');
const router = express.Router();
const fs = require('fs');
const raw = fs.readFileSync("./res/za-province-city.json");
const map = JSON.parse(raw);
const mapProvinceCity = {};
map.forEach(item => {
    if (!mapProvinceCity.hasOwnProperty(item.ProvinceName)) {
        mapProvinceCity[item.ProvinceName] = [];
    }
    mapProvinceCity[item.ProvinceName].push(item.City);
});
for (let key in mapProvinceCity) {
    mapProvinceCity[key].sort((a, b) => a.toLocaleString().localeCompare(b.toLowerCase()));
}

router.get('/location/provinces', function (req, res, next) {
    let strings = Object.keys(mapProvinceCity);
    let json = JSON.stringify({provinces: strings}).replace(/"/g, '');
    return res.json(json);
});

router.get('/location/:province/cities', function (req, res, next) {
    let province = req.param('province');
    let cities = mapProvinceCity[province];
    let json = JSON.stringify({cities: cities}).replace(/"/g, '');
    return res.json(json);
});

module.exports = router;

Note: if you are wondering about the replace(), each time I requested data in postman, I got

enter image description here

Phil
  • 128,310
  • 20
  • 201
  • 202
CybeX
  • 1,653
  • 27
  • 77
  • _"I receive the following data (taken from Chrome console)"_ where? What particular `console.log()` line produced that result? – Phil Aug 17 '20 at 00:53
  • 2
    Is the data you're getting back exactly as you've included here? Because that's not valid json. (No quotes.) If that's what's being returned from the server, the `e.json()` call is probably throwing. – ray hatfield Aug 17 '20 at 00:53
  • @Phil adding screenshots, give me a sec – CybeX Aug 17 '20 at 00:54
  • @rayhatfield see above comment – CybeX Aug 17 '20 at 00:55
  • A screenshot of the console might be helpful but what I'd really like to see is the code that produces it – Phil Aug 17 '20 at 00:55
  • @Phil yeah - it was my mistake - will post answer now – CybeX Aug 17 '20 at 01:03
  • @rayhatfield see above comment – CybeX Aug 17 '20 at 01:03
  • Oh looks like you are running the fetch in console and that's why you see the pending promise because that's what it returns. – charlietfl Aug 17 '20 at 01:06
  • 1
    What on Earth are you doing with that string replace? Also, you're double-encoding your mangled JSON so in the end, it will just be an unusable string. Just use `res.json({provinces: strings})` – Phil Aug 17 '20 at 01:07
  • @Phil it has been a while since I last worked with this ;). See my answer – CybeX Aug 17 '20 at 01:13

1 Answers1

3

I think your issues all stem from a misunderstanding of Express' res.json().

This is basically a shortcut for

res.set("Content-type: application/json")
res.status(200).send(JSON.stringify(data))

I imagine your problems started when you thought you needed to stringify your data. What happens then is that your data is double-encoded / double stringified, hence the extra quotes. Removing the quotes though mangles your data.

console.log() is not a particularly good debugging tool as it obfuscates a lot of information. In your code, s is actually a string

"{provinces:[Eastern Cape,Mpumalanga,...]}"

I suggest you use the actual debugger instead.


The simple solution is to use res.json() as intended

router.get('/location/provinces', function (req, res, next) {
  return res.json({ provinces: Object.keys(mapProvinceCity) });
});

with your client-side code looking like

fetch("http://localhost:3443/app/location/provinces")
  .then(res => {
    if (!res.ok) {
      throw res
    }
    return res.json()
  })
  .then(data => {
    console.log('Provinces:', data.provinces)
  })

This goes for all your Express routes. Do not use JSON.stringify().

Phil
  • 128,310
  • 20
  • 201
  • 202
  • You are correct about the `res.json()` being a shortcut for the header, etc. I recall going through that not to long ago. Hopefully it all comes back to me pretty soon :) – CybeX Aug 17 '20 at 01:24