devexperts/remote-data-ts

Remove RemoteInitial state

raveclassic opened this issue ยท 13 comments

In practice RemoteInitial doesn't represent any useful data state. Historically it was introduced basing on the article and to describe initial value in angular2 property bindings in classes.
However in 100% cases initial state is actually RemotePending.

This issue should gain some feedback before removing.

scink commented

how about mark as 'deprecated' first?

I don't necessarily agree with this. Conceptually RemotePending and RemoteInitial are indeed two distinct states.

It's easy enough to treat them the same when you want to, but if you need the distinction and it's not there you're stuck.

@moljac024 if I would need to distinguish "loading" and "loading not started yet" I will probably use Option<RemoteData> because when loading not started, there is no RemoteData yet.

@sutarmin

True, but isn't RemoteInitial more precise than Option<RemoteData>? And on top of that precision, removing it is a huge breaking change. And for what?

@raveclassic

However in 100% cases initial state is actually RemotePending.

Perhaps to the view layer they're often the same, but if you're using something like Redux or any other state management pattern, there is a real difference. Namely, you have some initial state that doesn't get loaded until some user interaction, or possibly never in some user flows (think optional loading, or role-based UI).

Overloading RemotePending to mean "Maybe I've made a request and haven't gotten a response, or maybe I still need to make the request." feels so much like data: foo[] | null to me.

What problem are we trying to solve by removing this state?

@scotttrinh We are using RemoteData with RXJS and RemoteInitial in our case is an empty observable which totally makes more sense.

const userName$ = /*rxjs ajax*/ajax({ url: '/username' }).map(response => success(response.response)).catch(e => of(failure(e)).startWith(pending)

// userName$ is in initial state until it is subscribed

As another example let's think of some entity storage in a Redux store.

type EntityStorage<A> = {[ id: string ]: A }
type Store<A> = { entities: EntityStorage<A> }

If you have an ID you can try to load the entity from the store but there's no RemoteInitial inside it, there's not value at all. Then you end up manually putting RemoteInitial into the store but this makes no sense because at this moment you already ask for a value so it's more RemotePending.

const getValue = <A>(id: string): RemoteData<A> /* ??? */ => (store: Store<RemoteData<A>> /* note the type */) => store.entities[id] || initial /* ??? also how would you put it into the store? another action or saga or smth? */ 

@raveclassic

I do like that pattern, but what about non-collection-like entities such as /me? I'd imagine that'd be modeled more like:

interface ProfileState {
  me: RemoteData<Error, Profile>;
}

const initialState = { me: initial };

And for the case where you're not logged in, it would stay RemoteInitial.

I'd model it differently - if you are not logged in, then you have no (and should not) access to me at all.

Then when you're successfully logged in you (possibly) mount some "user profile" container which is able to fetch that me data starting with pending.

This is easily applied to rxjs and decentralized state-containers/services/models, but not to a single global redux store.

@raveclassic

Good point! I still don't think you would "start with pending" in Redux (at least I wouldn't in my current app) until some component mounted that called the action that actually made the request, but I can see where you're coming from.

I suppose my point (to mirror what @moljac024 has already said) is that RemoteInitial is indeed a separate/distinct state, and further, it's a pretty big breaking change (everyone would have to update a large portion of their remote-data code) for an as-of-yet-unmentioned benefit.

Perhaps we could have another data structure that does not have the RemoteInitial state that you can easily cast? RequestState?

Not a real user of this library, just adding my two cents based on a quick experiment with it:

@raveclassic

We are using RemoteData with RXJS and RemoteInitial in our case is an empty observable which totally makes more sense.

I found myself using RemoteInitial as the initial value for a BehaviorSubject (not directly exposed to consumers of the data, but handy for internal representation the lib)

@scotttrinh

Perhaps we could have another data structure that does not have the RemoteInitial state that you can easily cast? RequestState?

Related to my point above, I did exactly this: expose the behavior subject asObservable<Pending | Success | Failure> to consumers, which are never interested in the Initial case

@raveclassic

  1. Queues: throttling an array of requests, two to three at a time, from a list of 20 or more. Not all requests are in pending immediately and the initial state represents their being queued but not in progress.
  2. Chains: when one request must be completed before another but the data is stored separately but is contingent upon the first being completed.
  3. Authorization information before logging in.

There are so many reasons to keep RemoteInitial. I hope these examples make sense to you. :D

Oops, sorry.

@raveclassic I caught myself thinking that we are trying to restrict ways RemoteData can be used without any profit from it. It is just a data structure, I think the argument "You can remodel your data to go without RemoteInitial" is not valid here. If it's convenient to use RemoteInitial with some approaches, why not to allow it? You see, folks have cases to use it :)

Hi everybody, I'll write here what I (wrongly) wrote in a separate issue

I borrow from both positions as I can see potential use cases of RemoteInitial but I also feel that it is a "second class" citizen compared to the other tags. I also feel that with the current implementation the fold function is overly verbose most of the times (you always have to handle RemoteInitial even though you really don't care 99% of times). I believe a more pragmatic solution would be a structure that hides the initial "detail" behind a more comprehensive tag, something like:

type RemoteNotReady = RemoteInitial | RemoteLoading
type RemoteData = RemoteNotReady | RemoteFailure | RemoteSuccess

I don't agree that RemotePending is the initial state. We use RemoteData in React+Redux app, and our use case is the following:

  1. Initially all network-related store fields are set to initial to represent the fact that the user hasn't requested data loading. Folding over this state results in rendering a button-ish element which dispatches action for loading data.
  2. After such action is dispatched, the field is set to pending to represent the fact that the data is still loading. Folding over this state results in rendering a progress bar.
  3. When the request is finished, the field is set to either failure or success.

So I strongly vote for keeping the RemoteInitial class, too.