Empty2k12
Empty2k12

Reputation: 495

ReactJS setState from asynchronous callback

Hey ReactJs Community,

I am fairly new to ReactJs and have gotten my first components set up. Now I'm at a point where I would like to update all state items on a specific event. I am looping each state item via map() and am calling a asynchronous method to determine a value to be included in the state. This method is returning GET data via the callback function.

updateItems: function() {
    var items = this.state.items;
    items.map(function(item, i) {
        checkConfirmation(item.hash, function(confirmations) {
            console.log("Item " + i + " has " + confirmations + " confirmations!");
            items[i].hash = item.hash + " (" + confirmations + ")";
            items[i].completed = true;
        });
    });
}

How can I update my state from the asynchronous callback?

I tried passing in this as the second parameter in map() and I tried calling setState after the map() function, but that cannot work since it will be called before any data is returned in the callback.

Thank you for taking time to help me with this issue!

Upvotes: 2

Views: 1680

Answers (1)

Yury Tarabanko
Yury Tarabanko

Reputation: 45121

You can make a promise for each pending request. Await them all using Promise.all. And set state when all requests are done.

updateItems: function() {
    const items = this.state.items;

    const pending = items.map(item => new Promise(resolve => {
        checkConfirmation(item.hash, resolve)
    }))

    Promise.all(pending)
      .then(confirmations => confirmations.map((confirmation, i) => {
        const item = items[i]

        // copy items mutating state is bad
        return Object.assign({}, item, {
          completed: true,
          hash: `${item.hash}(${confirmation})`
        })
      }))
      .then(items => this.setState({ items })
}

UPD Callbacks hackery

updateItems: function() {
    const items = this.state.items;
    let confirmations = []
    let loaded = 0

    const addConfirmation = i => confirmation => {
      confirmations[i] = confirmation
      loaded++;

      // if all were loaded
      if(loaded === items.length) allConfirmationsLoaded()
    }

    const allConfirmationsLoaded = () => {
        const newItems = confirmations.map((confirmation, i) => {
            const item = items[i]

            // copy items mutating state is bad
            return Object.assign({}, item, {
              completed: true,
              hash: `${item.hash}(${confirmation})`
            })
        })

        this.setState({items: newItems})
    }

    // for each item lauch checkConfirmation
    items.forEach((item, i) => {
      checkConfirmation(item.hash, addConfirmation(i))
    })
}

Upvotes: 2

Related Questions