1

I have a component that must make an HTTP request based off new props. Currently it's taking a while to actually update, so we've implemented a local store that we'd like to use to show data from past requests and then show the HTTP results once they actually arrive.

I'm running into issues with this strategy:

componentWillRecieveProps(nextProps){
  this.setState({data:this.getDataFromLocalStore(nextProps.dataToGet)});
  this.setState({data:this.makeHttpRequest(nextProps.dataToGet)});
  //triggers single render, only after request gets back
}

What I think is happening is that react bundles all the setstates for each lifecycle method, so it's not triggering render until the request actually comes back.

My next strategy was this:

componentWillRecieveProps(nextProps){
  this.setState({data:this.getDataFromLocalStore(nextProps.dataToGet)});
  this.go=true;
}
componentDidUpdate(){
  if(this.go){
    this.setState({data:this.makeHttpRequest(this.props.dataToGet)});
  }
  this.go=false;
}
//triggers two renders, but only draws 2nd, after request gets back

This one SHOULD work, it's actually calling render with the localstore data immediately, and then calling it again when the request gets back with the request data, but the first render isnt actually drawing anything to the screen!

It looks like react waits to draw the real dom until after componentDidUpdate completes, which tbh, seems completely against the point to me.

Is there a much better strategy that I could be using to achieve this?

Thanks!

Joe Roddy
  • 729
  • 6
  • 12

2 Answers2

1

One strategy could be to load the data using fetch, and calling setState when the data has been loaded with the use of promises.

componentWillRecieveProps(nextProps){
  this.loadData(nextProps)
}

loadData(nextProps){
    // Create a request based on nextProps
    fetch(request)
        .then(response => response.json())   
        .then(json => this.setState({updatedValue: json.value})
}
Wasif Hyder
  • 1,309
  • 2
  • 10
  • 17
  • 1
    Right, but assuming my "request" function is the same as your "loadData" (any sort of async request, fetch, ajax, whatever it may be), how would you render the component with some default data ("dataFromStore()") before the async request gets back? – Joe Roddy Mar 24 '17 at 04:47
  • @JoeRoddy - You should have a getInitialState function that returns the default value for that data. The initial data will then be empty, or some default value, until your async request completes and updates the data. – Wasif Hyder Mar 24 '17 at 04:49
0

I use the pattern bellow all the time (assuming your request function supports promises)

const defaultData = { /* whatever */ }

let YourComponent = React.createClass({
  componentWillRecieveProps: function(nextProps) {
    const that = this

    const cachedData = this.getDataFromLocalStore(nextProps)
    that.setState({
      theData: { loading: true, data: cachedData }
    })

    request(nextProps)
      .then(function(res) {
        that.setState({
          theData: { loaded: true, data: res }
        })
      })
      .catch(function() {
        that.setState({
          theData: { laodingFailed: true }
        })
      })
  },

  getInitialState: function() {
    return {
      theData: { loading: true, data: defaultData }
    };
  },

  render: function() {
    const theData = this.state.theData

    if(theData.loading) { return (<div>loading</div>) } // you can display the cached data here
    if(theData.loadingFailed) { return (<div>error</div>) }

    if(!theData.loaded) { throw new Error("Oups") }

    return <div>{ theData.data }</div>
  }

)}

More information about the lifecycle of components here

By the way, you may think of using a centralized redux state instead of the component state.

Also my guess is that your example is not working because of this line:

this.setState({data:this.makeHttpRequest(this.props.dataToGet)});

It is very likely that makeHttpRequest is asynchronous and returns undefined. In other words you are setting your data to undefined and never get the result of the request...

Edit: about firebase

It looks like you are using firebase. If you use it using the on functions, your makeHttpRequest must look like:

function(makeHttpRequest) {
  return new Promise(function(resolve, reject) {
    firebaseRef.on('value', function(data) {
      resolve(data)
    })
  })
}

This other question might also help

Community
  • 1
  • 1
Antoine Trouve
  • 1,198
  • 10
  • 21
  • The problem is that my goal isn't to load "default data" persay, it's some data that I generate on then client that is based on nextProps. This strategy would let me have a default state of "loading" or something, but my goal is to grab results of the request that I'm making with nextProps based out of my local history of request results, and show that on the screen until the request comes back. – Joe Roddy Mar 24 '17 at 05:10
  • You mean that your `request` function takes in input what you already have in the local store ? – Antoine Trouve Mar 24 '17 at 05:14
  • Yes so, based on something in nextProps, I'm making an http request. If I have a cached version of that request, I want to display the previous result until the response gets back. – Joe Roddy Mar 24 '17 at 05:16
  • That doesn't work (maybe I'm just an idiot). Essentially that is the first example I gave/how I would think it works intuitively, but it seems like react batches the setStates in componentwillreceiveprops and executes them all at the same time (and only after the request gets back). – Joe Roddy Mar 24 '17 at 05:33
  • I added a line in my answer about asynchronous calls. Especially, http requests are asynchronous in javascript, meaning that you likely cannot call `this.makeHttpRequest` the way you do. Can you tell use what is inside it ? – Antoine Trouve Mar 24 '17 at 05:35
  • It's a firebase call, but it should function just like a fetch. Just to be clear, the http request works, its the drawing of the cached results (ie to make it look faster) is what's not working. – Joe Roddy Mar 24 '17 at 05:40
  • Precisely `fetch` is async, this means that if you write `const data = fetch(something)`, `data` will hold a fetch object, not your data. You need to call it this way: `fetch(something).then(function(res) { data = res }).catch(...)`. Maybe you can show us the exact contents of `this.makeHttpRequest` – Antoine Trouve Mar 24 '17 at 05:45
  • @JoeRoddy - What if you try to separate the two functions? First, retrieve the data from cache using getDataFromCache. Since its sequential, it'll run immediately. Then pass in a callback that updates your state once the loadData call completes – Wasif Hyder Mar 24 '17 at 06:12