j_quelly
j_quelly

Reputation: 1449

Fetch() is not returning data from server in response

I'm having an issue with my fetch returning any data from the server and I can't quite seem to understand why.

I thought maybe the promise was resolving before the server was sending back any JSON so I recently updated my model to utilize promises as well. I'm new to promises and the fetch API so bear with me.

I am willing to revert back too callbacks and put my Player.find and Player.create methods back in the Players.js route and avoid promises on the server side altogether.

Player Model (after adding promise)

// require mongoose for data modeling
import mongoose from 'mongoose';
import Q from 'q';

const Schema = mongoose.Schema;

// define player schema
const PLAYER_SCHEMA = new Schema({
  name: String,
  score: Number
});

PLAYER_SCHEMA.statics.createPlayer = (name, score) => {
  const deferred = Q.defer();

  Player.create({
    name,
    score,
  }, (error, player) => {
    if (error) {
      deferred.reject(new Error(error));
    }

    deferred.resolve(player);
  });

  return deferred.promise;
}

PLAYER_SCHEMA.statics.getPlayers = () => {
  const deferred = Q.defer();

  Player.find({})
    .sort({
      score: 'desc'
    })
    .limit(5)
    .exec((error, players) => {
        if (error) {
          deferred.reject(new Error(error));
        }

        deferred.resolve(players);
  });

  return deferred.promise;
}

let Player = mongoose.model('Player', PLAYER_SCHEMA);

// first param is the singular name for the collection,
// mongoose automatically looks for the plural version
// so our db will have a 'players' collection
export default Player;

Route (after adding promise)

// dependencies
import express from 'express';
import Player from '../models/player';

const ROUTER = express.Router();

ROUTER.post('/', (req, res) => {
  const name = req.body.name;
  const score = req.body.score;
  const promise = Player.createPlayer(name, score);

  promise.then((response) => {
    res.status(201).json(response);
  }).catch((err) => {
    res.send(err)
  });

});

ROUTER.get('/', (req, res) => {
  const promise = Player.getPlayers();

  promise.then((response) => {
    console.log(response);
    res.status(200).json(response);
  }).catch((err) => {
    res.send(err)
  });
});

export default ROUTER;

Testing these endpoints works fine in Postman, so I assume the issue does not lie on the server.

This is where I suspect I have an issue.

Client.js (where AJAX methods are stored)

function createPlayer(playerData, onError, cb) {
  return fetch('/api/players', {
    method: 'post',
    body: JSON.stringify(playerData),
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
  }).then(checkStatus)
    .then(cb)
    .catch(onError);
}

function getPlayers(success, onError) {
  return fetch('/api/players', {
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
  }).then(checkStatus)
    .then(parseJSON)
    .then(success)
    .catch(onError);
}

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  } else {
    const error = new Error(`HTTP Error ${response.statusText}`);
    error.status = response.statusText;
    error.response = response;
    throw error;
  }
}

function parseJSON(response) {
  return response.json();
}

const Client = {
  createPlayer,
  getPlayers
};
export default Client;

App.jsx (a lot of code omitted)

// all other code omitted

  const playerData = {
    name,
    score: this.state.totalScore,
  };

  client.createPlayer(playerData,
    (err) => {
      // TODO: improve error message
      console.log(err);
    },
    client.getPlayers((data) => {
      // array of objects containing all but most recent entry
      console.log(data); 
      this.setState({
        inputScreen: false, // hide the input screen
        highScoreScreen: true, // show the high score screen
        yeti: yetiWin,
        highScores: [...this.state.highScores, ...data],
      });
    }, (err) => {
      // TODO: improve this error message
      console.log(err);
    })
  );   

// all other code omitted

The data variable never contains the newly created player, but it does contain other players. So the create and read operations are working on the server-side. When I modify my App.jsx to something like below:

App.jsx (trial)

  const playerData = {
    name,
    score: this.state.totalScore,
  };

  client.createPlayer(playerData,
    (err) => {
      // TODO: improve error message
      console.log(err);
    },
    (res) => {
      // should contain something like:
      // {
      //     "__v": 0,
      //     "_id": "5881922f441fda40ccc52f83",
      //     "name": "player name",
      //     "score": "123"
      // }
      // right?
      console.log(res.json());
      console.log(res);
      console.log(res.body);
    }
  );   

When I log res.json res and res.body I get:

Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}

Response {type: "basic", url: "http://localhost:3000/api/players", status: 200, ok: true, statusText: "OK"…}

ReadableStream {}

When I test my POST endpoint (http://localhost:3001/api/players) in postman the server responds with a JSON object containing the data I want. For whatever reason client.createPlayer doesn't return the data from the server. Of course, that's only part of the problem.

In theory, once my client.createPlayer resolves and I invoke client.getPlayers as the callback to client.createPlayer that method should return the data I want including the newly created player, right?

However that doesn't seem to be the case.

What am I doing wrong?

Upvotes: 1

Views: 1884

Answers (1)

j_quelly
j_quelly

Reputation: 1449

Interestingly enough, by changing the code in my App.jsx to the following I was able to produce the results I'm after. However, I'm not going to mark my answer as the correct solution as I do not fully understand why this change made it work.

If anyone would like to chime in and explain why this now works I would love to hear the reason why.

App.jsx (fixed)

// all other code omitted 

  client.createPlayer(playerData, (err) => {
    console.error(err);
  }, (data) => {
    this.setState({
      highScores: [...this.state.highScores, ...data],
    });
  });      

  client.getPlayers((data) => {
    this.setState({
      inputScreen: false, // hide the input screen
      highScoreScreen: true, // show the high score screen
      yeti: yetiWin,
      highScores: [...this.state.highScores, ...data],
    });
  }, (err) => {
    console.error(err);
  });

// all other code omitted

You'll note that I am no longer invoking client.getPlayers as the success callback to client.createPlayer. On top of that I'm using the returned data from client.createPlayer and adding it to the state as a fallback in the event the client.createPlayer method does not finish before the client.getPlayers method. This would ensure that the new player is displayed once setState issues a re-render.

Upvotes: 1

Related Questions