Skip to content

RFC: Controller.fetchIfStale() #2053

@ntucker

Description

@ntucker

Motivation

  • fetch-as-render pattern results in over-fetching (non-stale data is fetched on every transition)
  • centralize more handling - less in hooks = happier world

Current world

Currently final ‘stale’ logic exists in hooks

const { data, expiryStatus, expiresAt } = controller.getResponse(endpoint, ...args, state);
const forceFetch = expiryStatus === ExpiryStatus.Invalid
const maybePromise = useMemo(() => {
    // null params mean don't do anything
    if ((Date.now() <= expiresAt && !forceFetch) || !key) return;

    return controller.fetch(endpoint, ...(args as readonly [...Parameters<E>]));
    // we need to check against serialized params, since params can change frequently
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [expiresAt, controller, key, forceFetch, state.lastReset]);

This isn’t too bad; but it does mean some logic is repeated in any implementation.

The non-lifecycle based logic can be extracted as such:

Date.now() <= expiresAt && expiryStatus !== ExpiryStatus.Invalid

Proposal

💡 Need feedback on name. I don’t like how long fetchIfStale is!

Add a new controller dispatch that will only fetch if the result is stale. If it ends up not fetching the promise will immediately resolve.

  • Only accepts sideEffect: false endpoints

Open questions

  • what should it be called
  • what should it resolve to as it might not fetch but that means the data already exists?
    • maybe denormalized form and waits until commit.
    • or perhaps the resolve value lets you know if it fetched at all
  • always returning a promise means it is unusable in hooks pre-react18. can we possibly hook up a callback in the middleware that determines whether it fetches non-async?

Dispatch resolution control

dispatch adds to action a callback. if this callback is called it returns the dispatch early.

Instead of

return next(action);
return Whatever;

And you can still continue processing the request by doing the rest of it async

// we don't wait on resolution
next(action);
return Whatever;

Doing this would also enable not having to send resolve/reject in fetch meta; but just take dispatch return value. This would be actual promise used by the NetworkManager so referential equality checks could be performed against it.

This means dispatch() now has a variable return type based on what the middlewares do. This could have weird implications for user-defined managers; so perhaps we should add type inference based on middlewares? This can be delayed tho.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions