Flux: returns values ​​from AJAX to component

I am working on a simple Flux + Reactjs app, in my store I have:

var ProfileStore = merge(EventEmitter.prototype, {
/**
   * Get the entire collection of Profiles.
   * @return {object}
   */
  getAll: function() {
    //ajax call to mongolab
    var url = "https://api.mongolab.com/api/1/databases/bar/collections/profiles?apiKey=foo-"
    var jsPromise = Promise.resolve($.ajax(url));
    jsPromise.then(function(response) {
        return response;
    });

  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  /**
   * @param {function} callback
   */
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  /**
   * @param {function} callback
   */
  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }
});

      

and then in my component I:

var ProfileApp = React.createClass({

    getInitialState: function() {
        return {
            allProfiles: ProfileStore.getAll()
        };
    },

});

      

When I console.log () in the getAll () function I see the results, but nothing is being passed to my component, any pointers on how I can fix this?

+3


source to share


3 answers


Others have pointed out promises related issues in your code. However, another problem with what you are trying to do in the store is that you are trying to process the response directly instead of dispatching a new action.

In Flux, data must always start with an action. The function getAll()

should just return a locally stored one allProfiles

, similar to the one suggested by andrewkshim, but without the XHR.

Instead, break down what you are trying to do into two actions with the following types:



PROFILES_REQUEST_GET

: this is created by the user clicking a button, or by whatever triggers the request - it triggers the XHR request.

PROFILES_REQUEST_SUCCESS

: This is the action created by the XHR success callback. This action contains new data and the store can fill it allProfiles

accordingly.

+3


source


Tuning with Flux requires you to think differently about how the data comes in. What you can do is configure the repository to contain a reference to allProfiles

, which is undefined

(or null

whatever "empty" value you choose) first, and then updated whenever getAll()

called:

var allProfiles;  // the reference to your profile data

var ProfileStore = merge(EventEmitter.prototype, {

  getAll: function() {
    var thisStore = this;

    var url = "YOUR MONGO URL"
    var jsPromise = Promise.resolve($.ajax(url));
    jsPromise.then(function(response) {
        allProfiles = response
        this.emitChange();
    });

    return allProfiles;
  }

// other methods omitted to save space

      

I must point out that your use of promises is wrong here. The function then

returns a different promise, so you are setting allProfiles

on a promise, not a collection. In the example above, we are returning a link to the profile data that will be updated after the AJAX call completes. After the data is updated, the store emits a change so that all its listeners know that they should be updated too.

In your component, you can configure it to listen CHANGE_EVENT

on your component so ProfileStore

that it updates its state whenever the data changes ProfileStore

. Please note that at the very beginning, the data allProfiles

will be empty, so we will need to convey to the user that we are loading data:

var ProfileApp = React.createClass({

  updateAllProfiles: function () {
    this.setState({
      allProfiles: ProfileStore.getAll()
    });
  },

  getInitialState: function () {
    return {
      allProfiles: ProfileStore.getAll()
    }
  },

  componentDidMount: function() {
    ProfileStore.addChangeListener(this.updateAllProfiles);
  },

  componentWillUnmount: function () {
    ProfileStore.removeChangeListener(this.updateAllProfiles)
  },

  render: function () {
    var profiles;
    if (this.state.allProfiles) {
      // you'll need to figure out how you want to render the profiles
      // the line below will likely not be enough
      profiles = this.state.allProfiles;  
    } else {
      profiles = 'Loading...';
    }
    return <div id='profiles'>{profiles}</div>;
  }

});

      



I made a VERY contrived JSFiddle example. Due to environmental constraints, I had to come up with a couple of simulated hacks, but I hope you can tinker with the example and get a better understanding of what's going on: http://jsfiddle.net/berh6gxs/2/

I also masked a lot of details (for example, you can implement a method shouldComponentUpdate

on your component ProfileApp

so that it only re-displays when the data actually changed), but I felt these details were less important to answer the question posed. I mention this because you won't be able to take what I said here and immediately come up with a solution that works great.

I also suggest taking a look at third party Flux implementations. Yahoo came up with something that's pretty good: https://github.com/yahoo/flux-examples

Happy hack!

+2


source


This code:

jsPromise.then(function(response) {
    return response;
});

      

does not do what I think it does, you think it does. It waits for a resolution jsPromise

, calls a callback with a response, and whatever you return from the callback becomes the resolved value of the new promise that is returned from jsPromise.then

. Check out this JavaScript Promises tutorial for more information on how Promises work.

Regarding your question: in short, you cannot return a value from an asynchronous operation; you will always have to resort to a callback. For example:

var ProfileStore = merge(EventEmitter.prototype, {
  // getAll returns a Promise that resolves to all the profiles
  getAll: function() {
    var url = "..."
    return Promise.resolve($.ajax(url));
  },
  // ...
});

var ProfileApp = React.createClass({
  getInitialState: function() {
    return {
      allProfiles: [] // initially we have no profiles
                      // could also have some "loading" sentinel, etc
    };
  },

  componentDidMount: function() {
    // Then we ask for all the profiles and *asynchronously*
    // set the state so the component updates.
    ProfileStore.getAll().then(function(response) {
      this.setState({allProfiles: response});
    }.bind(this));
  }
});

      

+1


source







All Articles