Zeyukan Ich'
Zeyukan Ich'

Reputation: 693

Redux how I should handle update with API call

I have an application that triggers many update and I would like to know more about the best way to update the app properly.

In my app, I have 5 slots to fill with books (can be managed by drag and drop). When the app launches, the filled book for the user are loaded and are stored in the state.

Problem : when I update a book, like if I switch the position of 2 books in my list, I must do some operations to say "this book belongs here now and the other one belongs here now, switch!"

I feel like I'm doing some tedious actions because if I just return the whole data (get, after updating) from my API call and call the "load" function (as I do when I launch the app) I will not have to handle the update of the operation.

Plus, it could create bug If I'm loading correctly, but not updating correctly (if I miss position of a book for example)

The benefit I see in a functional update is that I only update the 2 books I need, instead of reload all of them again and again.

What way would be better? Should I get rid of those updates functions and just reload the data entirely? I think there could be also some libraries that cache it to only re-render modified books

Thanks you

Upvotes: 1

Views: 183

Answers (1)

HMR
HMR

Reputation: 39260

Without code it is difficult to fully understand the problem but getting the data from the server has 2 advantages.

  1. You are sure the ui shows the data as it is on the server
  2. Your client code does not need to contain the logic of what needs to happen, the server has this logic. When the logic is refactored in some way they don't go out of sync.

Because of this I usually choose to get the data as is on the server.

One problem with fetching data based on user interaction is that fetching is async so the following can happen:

User does action A, request made for A, user Does action B, request made for B, B request resolves and UI is set to result of request B, request made for A resolves and UI is set to result of A.

So the order the user does the actions does not guarantee the order in which the requests are resolved.

To solve this you can use a helper that resolves only if it was last requested, in the example above when A request resolves the UI does not need to be set with anything because it has already been replaced with another request.

In the example below you can type search value, when the value is 1 character long it'll take 2 seconds to resolve so when you type ab the ab request will resolve before the a request. but because the function making the request is wrapped with the last helper when a resolves it'll will be rejected because it has been replaced with the newer request ab.

//constant to reject with when request is replaced with a
//  more recent request
const REPLACED = {
  message: 'replaced by more recent request',
};
//helper to resolve only last requested promise
const last = (fn) => {
  const check = {};
  return (...args) => {
    const current = {};
    check.current = current;
    return Promise.resolve()
      .then(() => fn(...args))
      .then((result) => {
        //see if current request is last request
        if (check.current === current) {
          return result;
        }
        //was not last request so reject
        return Promise.reject(REPLACED);
      });
  };
};

const later = (howLong, value) =>
  new Promise((resolve) =>
    setTimeout(() => resolve(value), howLong)
  );
const request = (value) =>
  later(value.length === 1 ? 2000 : 10, value).then(
    (result) => {
      console.log('request resolved:', result);
      return result;
    }
  );
const lastRequest = last(request);
const App = () => {
  const [search, setSearch] = React.useState('');
  const [result, setResult] = React.useState('');
  React.useEffect(() => {
    //if you use request instead of lastRequest here
    //  you see it will break, UI is updated as requests
    //  resolve without checking if it was the last request
    lastRequest(search)
      .then((result) => setResult(`result:${result}`))
      .catch((err) => {
        console.log(
          'rejected with:',
          err,
          'for search:',
          search
        );
        if (err !== REPLACED) {
          //if the reject reason is not caused because request was
          //  replaced by a newer then reject this promise
          return Promise.reject(err);
        }
      });
  }, [search]);
  return (
    <div>
      <label>
        search
        <input
          type="text"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
        ></input>
      </label>
      <div>{result}</div>
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 1

Related Questions