Jared Meyering
Jared Meyering

Reputation: 1321

Persisting events for a period of time with React / Redux

So I have an app I'm working on with React/Redux. It involves autosaving an entity to a datastore via api as values are changed.

I want to present the user with a "loading" indicator while the entity is being saved and then indicate "Saved!" or some other message once the response comes back from the api and updates the entity's state.

I actually have this working just fine. My reducers are

case "UPDATE_ROUND_REQUEST":
    newState = {...state};
    newState[action.meta.id].isSaving = true;
    return newState;

case "UPDATE_ROUND":
    newState = {...state};
    newState[action.meta.id] = action.payload; // This response doesn't have the isSaving prop set
    return newState;

And my component props receive this entity and the component accesses the isSaving property to display the "saving" message I want

The only problem is that my api returns data in something like 50ms. Basically instant, instant enough that this message is never seen. From a UX perspective what I would like is for my component, when it detects via props that the entity is in the process of loading, to display the saving message for a reasonable period of time so that the user is confident that the data is saved.

I'm struggling with handling that in some sort of stateless way.

Any tips appreciated!

Upvotes: 1

Views: 354

Answers (1)

Yo Wakita
Yo Wakita

Reputation: 5450

I would love to hear other answers on this because it's something I think about as well. Here is one solution that I believe can work. There may be a more performant solution out there though, and it might not be preferable to delay the response from a reducer artificially like I do in my example.

  1. Inside of the initial dispatch of the action, you will save the current time as a date object.
  2. When the response is received, check to see how long it has been since the request was made.
  3. If it has been x amount of milliseconds since the request was made, then return the updated state immediately.
  4. If it has not been x amount of milliseconds, set a timeout with the difference between how long you want the loader to stay at minimum, minus the amount of time that has elapsed since the request was made.

The code below is untested but I believe conceptually it will work.

import moment from 'moment';

const round = (
  state = [],
  action,
) => {
  switch (action.type) {
    case "UPDATE_ROUND_REQUEST":
      const newState = {...state};
      newState[action.meta.id].isSaving= true;
      newState[action.meta.id].requestTime= moment(new Date());
      return newState;
    case "UPDATE_ROUND":
      // get difference between now and when the request was sent.
      const difference =  moment(new Date).diff(state[action.meta.id].requestTime);
      const newState = {...state};
      // reset isSaving to false, and changes requestTime back null (default)
      newState[action.meta.id] = {...action.payload, isSaving: false, requestTime: null};
      // if difference is greater than 3 seconds, return the new state immediately.
      if (difference > 3000) {
        // return new state with updated data for the value at newState[action.meta.id].
        return newState;
      }
      // else, if the difference is less than 3 seconds (3000ms), wait the difference and then return the new state
      setTimeout(() => {
        return newState;
      }, 3000 - difference);
    default: return state;
  }
};

Upvotes: 1

Related Questions