Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🗺️ Single Fetch #7641

Closed
ryanflorence opened this issue Oct 11, 2023 · 6 comments
Closed

🗺️ Single Fetch #7641

ryanflorence opened this issue Oct 11, 2023 · 6 comments

Comments

@ryanflorence
Copy link
Member

@mjackson
Copy link
Member

mjackson commented Dec 1, 2023

I was talking to @jacob-ebey about this and he suggested we need some hooks for encoding/decoding loader data. I've asked him to write more about that here.

@jacob-ebey
Copy link
Member

Single fetch is going to require exposing a few internal router concepts as public API such DataResult. The reasoning for this is single fetch implementor essentially replaces / skips our internal logic for "calling route loader / action functions".

Instead of calling this "Single fetch", let's call it the "data loader hook". The implementors responsibility will essentially be to return a DataResult per entry for routesToLoad. This is applicable for loaders and actions for both navigations and fetchers. Think of it as "call local loader / action function, or do something else completely to get the route data".

The data loader hook will also NOT necessarily be responsible for decoding individual loader / action Response content. The data loader hook will return DataResults (SuccessResult, DeferredResult, RedirectResult, or ErrorResult) that are then fed through our existing "decode / parse loader / action responses" depending on the type of result.

The existing "decode / parse" pipeline is locked down to just JSON / text responses and I believe unlocking this will be a bigger win than single fetch with less risk of exposing router implementation details.

The hooks into the decode pipeline would unlock less restrictive transport formats in existing remix, as well as enable RSC. The data loader hook would allow us to implement remix's current fetch logic in a shared location outside of loader()/ action() functions with a path towards combining them in a single network request.

(RR) means handled by the router, (U) means overridable by the user of react-router:

How I think about today's flow:

  1. Match routes (RR)
  2. Determine what routes to load (RR)
  3. Call loader / action functions (RR)
  4. Decode Responses (RR)

With decode hook:

  1. Match routes (RR)
  2. Determine what routes to load (RR)
  3. Call loader / action functions (RR)
  4. Decode Responses however you'd like (U)

With data loader hook:

  1. Match routes (RR)
  2. Determine what routes to load (RR)
  3. Maybe call loader / action functions (U)
  4. Decode Responses (RR)

With data loader hook and decode hook:

  1. Match routes (RR)
  2. Determine what routes to load (RR)
  3. Maybe call loader / action functions (U)
  4. Decode Responses however you'd like (U)

@ryanflorence
Copy link
Member Author

@jacob-ebey that sounds great to me. @brophdawg11 and I had discussed a long time ago that @remix-run/router will eventually need a pluggable "load the data" implementation, one use case we were thinking of was for SPAs that want to combine a bunch of graphql fragments into a single query and request. The encode/decode step will also make it much better for adding superjson, etc.

I'd get Matt's take on it, I know he's thought a lot about it already.

@jacob-ebey
Copy link
Member

Step 1: remix-run/react-router#11086

@brophdawg11
Copy link
Contributor

brophdawg11 commented Dec 5, 2023

We can talk about this on the call today but curious if we need 3/4 to be separate router APIs above? In the initial POC for fetchStrategy(remix-run/react-router#11001), it would have been responsible for both 3 and 4 (calling loaders and decoding their results).

I could probably be convinced pretty easily that fetchStrategy and decodeResponse as separate APIs is easier for the end user, but here's what I might envision for a single fetchStrategy API surface:

createBrowserRouter(routes, {
  fetchStrategy({ request, matches, defaultStrategy }) {
    // 1. Default loader execution / Default response decoding
    let results = await defaultStrategy();

    // 2. Default loader execution / Custom response decoding
    let results = await defaultStrategy((response) => customDecode(response));

    // 3. Custom loader execution / Default response decoding
    let results = Promise.all(matches.map(async (match) => {
      let response = await callLoaderCustom(match);
      // This is where it gets a bit tricky - no super clean way to expose a `defaultDecode()` 
      // function but I would argue we do not need to since it's just standard web fetch stuff 
      // and a one liner if you know your app only deals in JSON/whatever
      let data = await response.json();
      return { type: ResultType.Success, data };
    }));

    // 4. Custom loader execution / Custom response decoding
    let results = Promise.all(matches.map(async (match) => {
      let response = await callLoaderCustom(match);
      let data = await customDecode(response);
      return { type: ResultType.Success, data };
    }));
  }
});

The comment in code snippet (3) above is what I was getting at in this comment as well.

@brophdawg11
Copy link
Contributor

@jacob-ebey @ryanflorence Should we should consider #4319 as a potential add-on or fast-follow to this work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

No branches or pull requests

4 participants