Optimistic UI update in React using setState()

Share this video with your friends

Send Tweet

In this lesson we will refactor an existing UI update from a typical loading approach to an optimistic UI update approach to give our users a faster, more snappy experience. Instead of displaying a "loading" UI to our users while our request is in progress, we will immediately update the UI and account for reverting state and displaying an error in the event of a failure. We can accomplish this relatively easily in React, thanks to the simplicity and power of setState() combined with making use of Javascript's lexical scoping and closures.

Massimiliano Filacchioni
Massimiliano Filacchioni
~ 7 years ago

Nice approach! But how to avoid inconsistent state due to the fact that between taking the snapshot of current state and restoring it after a failure, the user could perform other actions?

For example if you delete item 2 immediately after item 3, item 3 disappears, then disappears item 2, but after item 3 deletion failure both are restored.

Erik Aybar
Erik Aybar(instructor)
~ 7 years ago

Great question, Massimiliano! The simplified approach used in this video may be fine for a number of scenarios, but is definitely not the most robust solution. This will make a great follow up lesson, but I'll try my best to answer your question here now.

how to avoid inconsistent state due to the fact that between taking the snapshot of current state and restoring it after a failure, the user could perform other actions? For example if you delete item 2 immediately after item 3, item 3 disappears, then disappears item 2, but after item 3 deletion failure both are restored.

There are a few ways to address the problem you've described, but I will keep it simple and comparable to the approach used in the video. Instead of taking a snapshot of the entire current state of items, we can take a snapshot of only the item being deleted so that we can more precisely restore the single item in the event of a failure.

You can play with this live on this Codesandbox demo and here is the relevant snippet:

class App extends React.Component {
  // ...

  deleteItemOptimistic = id => {
    // 1) Snapshot target item so we can restore it in the case of failure
    const deletingItem = this.state.items.find(item => item.id === id);

    // 2) Assume success. Immediately update state
    this.setState(state => ({
      items: state.items.filter(item => item.id !== id),
    }));

    // 3) If the request failed revert state and display error.
    deleteItemRequest(id).catch(() =>
      this.setState(state => ({
        // Restore the single, deleted item.
        // Use sort to ensure it is inserted where expected.
        items: [...state.items, deletingItem].sort((a, b) => a.id - b.id),
        error: `Request failed for item ${id}`,
      }))
    );
  };

  // ...
}
Massimiliano Filacchioni
Massimiliano Filacchioni
~ 7 years ago

Thank you for the quick reply, I was just playing with something similar. Very interesting subject. I can't wait for the follow up lesson, and I hope there will be more than one if needed (to cover all the ways to address the problem)!

Shawn Wang
Shawn Wang
~ 7 years ago

I was screaming in my mind exactly Massimiliano's corner case; its obvious enough that I would have addressed it in the video itself. anyway, nice video!

Dennis Dang
Dennis Dang
~ 7 years ago

lol i was too excited to even think about edge cases when i thought: "wth, i should've been doing these techniques a long time ago."