Skip to content

Commit

Permalink
Stabilize unstable_dataStrategy (#11974)
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 authored Sep 10, 2024
1 parent 25e4981 commit 09c1978
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 87 deletions.
7 changes: 7 additions & 0 deletions .changeset/nasty-queens-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-router-dom": minor
"react-router": minor
"@remix-run/router": minor
---

Stabilize `unstable_dataStrategy`
44 changes: 14 additions & 30 deletions docs/routers/create-browser-router.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function createBrowserRouter(
basename?: string;
future?: FutureConfig;
hydrationData?: HydrationState;
unstable_dataStrategy?: unstable_DataStrategyFunction;
dataStrategy?: DataStrategyFunction;
patchRoutesOnNavigation?: PatchRoutesOnNavigationFunction;
window?: Window;
}
Expand Down Expand Up @@ -184,15 +184,15 @@ const router = createBrowserRouter(
);
```

## `opts.unstable_dataStrategy`
## `opts.dataStrategy`

<docs-warning>This is a low-level API intended for advanced use-cases. This overrides React Router's internal handling of `loader`/`action` execution, and if done incorrectly will break your app code. Please use with caution and perform the appropriate testing.</docs-warning>

<docs-warning>This API is marked "unstable" so it is subject to breaking API changes in minor releases</docs-warning>

By default, React Router is opinionated about how your data is loaded/submitted - and most notably, executes all of your loaders in parallel for optimal data fetching. While we think this is the right behavior for most use-cases, we realize that there is no "one size fits all" solution when it comes to data fetching for the wide landscape of application requirements.

The `unstable_dataStrategy` option gives you full control over how your loaders and actions are executed and lays the foundation to build in more advanced APIs such as middleware, context, and caching layers. Over time, we expect that we'll leverage this API internally to bring more first class APIs to React Router, but until then (and beyond), this is your way to add more advanced functionality for your applications data needs.
The `dataStrategy` option gives you full control over how your loaders and actions are executed and lays the foundation to build in more advanced APIs such as middleware, context, and caching layers. Over time, we expect that we'll leverage this API internally to bring more first class APIs to React Router, but until then (and beyond), this is your way to add more advanced functionality for your applications data needs.

### Type Declaration

Expand Down Expand Up @@ -232,7 +232,7 @@ interface DataStrategyResult {

### Overview

`unstable_dataStrategy` receives the same arguments as a `loader`/`action` (`request`, `params`) but it also receives 2 new parameters: `matches` and `fetcherKey`:
`dataStrategy` receives the same arguments as a `loader`/`action` (`request`, `params`) but it also receives 2 new parameters: `matches` and `fetcherKey`:

- **`matches`** - An array of the matched routes where each match is extended with 2 new fields for use in the data strategy function:
- **`match.shouldLoad`** - A boolean value indicating whether this route handler should be called in this pass
Expand All @@ -247,9 +247,9 @@ interface DataStrategyResult {
- It is safe to call `match.resolve` for all matches, even if they have `shouldLoad=false`, and it will no-op if no loading is required
- You should generally always call `match.resolve()` for `shouldLoad:true` routes to ensure that any `route.lazy` implementations are processed
- See the examples below for how to implement custom handler execution via `match.resolve`
- **`fetcherKey`** - The key of the fetcher we are calling `unstable_dataStrategy` for, otherwise `null` for navigational executions
- **`fetcherKey`** - The key of the fetcher we are calling `dataStrategy` for, otherwise `null` for navigational executions

The `dataStrategy` function should return a key/value object of `routeId -> DataStrategyResult` and should include entries for any routes where a handler was executed. A `DataStrategyResult` indicates if the handler was successful or not based on the `DataStrategyResult["type"]` field. If the returned `DataStrategyResult["result"]` is a `Response`, React Router will unwrap it for you (via `res.json` or `res.text`). If you need to do custom decoding of a `Response` but want to preserve the status code, you can use the `unstable_data` utility to return your decoded data along with a `ResponseInit`.
The `dataStrategy` function should return a key/value object of `routeId -> DataStrategyResult` and should include entries for any routes where a handler was executed. A `DataStrategyResult` indicates if the handler was successful or not based on the `DataStrategyResult["type"]` field. If the returned `DataStrategyResult["result"]` is a `Response`, React Router will unwrap it for you (via `res.json` or `res.text`). If you need to do custom decoding of a `Response` but want to preserve the status code, you can use the `data` utility to return your decoded data along with a `ResponseInit`.

### Example Use Cases

Expand All @@ -259,7 +259,7 @@ In the simplest case, let's look at hooking into this API to add some logging fo

```ts
let router = createBrowserRouter(routes, {
async unstable_dataStrategy({ request, matches }) {
async dataStrategy({ request, matches }) {
// Grab only the matches we need to run handlers for
const matchesToLoad = matches.filter(
(m) => m.shouldLoad
Expand Down Expand Up @@ -290,7 +290,7 @@ If you want to avoid the `reduce`, you can manually build up the `results` objec

```ts
let router = createBrowserRouter(routes, {
async unstable_dataStrategy({ request, matches }) {
async dataStrategy({ request, matches }) {
const matchesToLoad = matches.filter(
(m) => m.shouldLoad
);
Expand Down Expand Up @@ -353,11 +353,7 @@ const routes = [
];

let router = createBrowserRouter(routes, {
async unstable_dataStrategy({
request,
params,
matches,
}) {
async dataStrategy({ request, params, matches }) {
// Run middleware sequentially and let them add data to `context`
let context = {};
for (const match of matches) {
Expand Down Expand Up @@ -426,7 +422,7 @@ const routes = [
];

let router = createBrowserRouter(routes, {
unstable_dataStrategy({ request, params, matches }) {
dataStrategy({ request, params, matches }) {
// Compose route fragments into a single GQL payload
let gql = getFragmentsFromRouteHandles(matches);
let data = await fetchGql(gql);
Expand Down Expand Up @@ -481,10 +477,7 @@ const router = createBrowserRouter(
},
],
{
async patchRoutesOnNavigation({
path,
patch,
}) {
async patchRoutesOnNavigation({ path, patch }) {
if (path === "/a") {
// Load/patch the `a` route as a child of the route with id `root`
let route = await getARoute();
Expand Down Expand Up @@ -512,10 +505,7 @@ const router = createBrowserRouter(
},
],
{
async patchRoutesOnNavigation({
path,
patch,
}) {
async patchRoutesOnNavigation({ path, patch }) {
if (path === "/root-sibling") {
// Load/patch the `/root-sibling` route as a sibling of the root route
let route = await getRootSiblingRoute();
Expand All @@ -540,10 +530,7 @@ let router = createBrowserRouter(
},
],
{
async patchRoutesOnNavigation({
path,
patch,
}) {
async patchRoutesOnNavigation({ path, patch }) {
if (path.startsWith("/dashboard")) {
let children = await import("./dashboard");
patch(null, children);
Expand Down Expand Up @@ -598,10 +585,7 @@ let router = createBrowserRouter(
},
],
{
async patchRoutesOnNavigation({
matches,
patch,
}) {
async patchRoutesOnNavigation({ matches, patch }) {
let leafRoute = matches[matches.length - 1]?.route;
if (leafRoute?.handle?.lazyChildren) {
let children =
Expand Down
8 changes: 4 additions & 4 deletions packages/react-router-dom-v5-compat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ export type {
ActionFunctionArgs,
AwaitProps,
BrowserRouterProps,
unstable_DataStrategyFunction,
unstable_DataStrategyFunctionArgs,
unstable_DataStrategyMatch,
unstable_DataStrategyResult,
DataStrategyFunction,
DataStrategyFunctionArgs,
DataStrategyMatch,
DataStrategyResult,
DataRouteMatch,
DataRouteObject,
ErrorResponse,
Expand Down
16 changes: 8 additions & 8 deletions packages/react-router-dom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type {
RouterProps,
RouterProviderProps,
To,
unstable_DataStrategyFunction,
DataStrategyFunction,
PatchRoutesOnNavigationFunction,
} from "react-router";
import {
Expand Down Expand Up @@ -106,10 +106,10 @@ export type {
BlockerFunction,
DataRouteMatch,
DataRouteObject,
unstable_DataStrategyFunction,
unstable_DataStrategyFunctionArgs,
unstable_DataStrategyMatch,
unstable_DataStrategyResult,
DataStrategyFunction,
DataStrategyFunctionArgs,
DataStrategyMatch,
DataStrategyResult,
ErrorResponse,
Fetcher,
FutureConfig,
Expand Down Expand Up @@ -258,7 +258,7 @@ interface DOMRouterOpts {
basename?: string;
future?: Partial<Omit<RouterFutureConfig, "v7_prependBasename">>;
hydrationData?: HydrationState;
unstable_dataStrategy?: unstable_DataStrategyFunction;
dataStrategy?: DataStrategyFunction;
patchRoutesOnNavigation?: PatchRoutesOnNavigationFunction;
window?: Window;
}
Expand All @@ -277,7 +277,7 @@ export function createBrowserRouter(
hydrationData: opts?.hydrationData || parseHydrationData(),
routes,
mapRouteProperties,
unstable_dataStrategy: opts?.unstable_dataStrategy,
dataStrategy: opts?.dataStrategy,
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
window: opts?.window,
}).initialize();
Expand All @@ -297,7 +297,7 @@ export function createHashRouter(
hydrationData: opts?.hydrationData || parseHydrationData(),
routes,
mapRouteProperties,
unstable_dataStrategy: opts?.unstable_dataStrategy,
dataStrategy: opts?.dataStrategy,
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
window: opts?.window,
}).initialize();
Expand Down
8 changes: 4 additions & 4 deletions packages/react-router-native/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export type {
BlockerFunction,
DataRouteMatch,
DataRouteObject,
unstable_DataStrategyFunction,
unstable_DataStrategyFunctionArgs,
unstable_DataStrategyMatch,
unstable_DataStrategyResult,
DataStrategyFunction,
DataStrategyFunctionArgs,
DataStrategyMatch,
DataStrategyResult,
ErrorResponse,
Fetcher,
FutureConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3210,7 +3210,7 @@ describe("createMemoryRouter", () => {
/>
</Route>
),
{ initialEntries: ["/foo"], unstable_dataStrategy: urlDataStrategy }
{ initialEntries: ["/foo"], dataStrategy: urlDataStrategy }
);
let { container } = render(<RouterProvider router={router} />);

Expand Down
20 changes: 10 additions & 10 deletions packages/react-router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import type {
ActionFunctionArgs,
Blocker,
BlockerFunction,
unstable_DataStrategyFunction,
unstable_DataStrategyFunctionArgs,
unstable_DataStrategyMatch,
unstable_DataStrategyResult,
DataStrategyFunction,
DataStrategyFunctionArgs,
DataStrategyMatch,
DataStrategyResult,
ErrorResponse,
Fetcher,
HydrationState,
Expand Down Expand Up @@ -136,10 +136,10 @@ export type {
AwaitProps,
DataRouteMatch,
DataRouteObject,
unstable_DataStrategyFunction,
unstable_DataStrategyFunctionArgs,
unstable_DataStrategyMatch,
unstable_DataStrategyResult,
DataStrategyFunction,
DataStrategyFunctionArgs,
DataStrategyMatch,
DataStrategyResult,
ErrorResponse,
Fetcher,
FutureConfig,
Expand Down Expand Up @@ -302,7 +302,7 @@ export function createMemoryRouter(
hydrationData?: HydrationState;
initialEntries?: InitialEntry[];
initialIndex?: number;
unstable_dataStrategy?: unstable_DataStrategyFunction;
dataStrategy?: DataStrategyFunction;
patchRoutesOnNavigation?: PatchRoutesOnNavigationFunction;
}
): RemixRouter {
Expand All @@ -319,7 +319,7 @@ export function createMemoryRouter(
hydrationData: opts?.hydrationData,
routes,
mapRouteProperties,
unstable_dataStrategy: opts?.unstable_dataStrategy,
dataStrategy: opts?.dataStrategy,
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
}).initialize();
}
Expand Down
4 changes: 2 additions & 2 deletions packages/router/__tests__/ssr-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1602,7 +1602,7 @@ describe("ssr", () => {
let { query } = createStaticHandler(SSR_ROUTES);

let context = await query(createRequest("/custom"), {
unstable_dataStrategy: urlDataStrategy,
dataStrategy: urlDataStrategy,
});
expect(context).toMatchObject({
actionData: null,
Expand Down Expand Up @@ -2635,7 +2635,7 @@ describe("ssr", () => {
let data;

data = await queryRoute(createRequest("/custom"), {
unstable_dataStrategy: urlDataStrategy,
dataStrategy: urlDataStrategy,
});
expect(data).toBeInstanceOf(URLSearchParams);
expect((data as URLSearchParams).get("foo")).toBe("bar");
Expand Down
2 changes: 1 addition & 1 deletion packages/router/__tests__/utils/data-router-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ export function setup({
hydrationData,
future,
window: testWindow,
unstable_dataStrategy: dataStrategy,
dataStrategy: dataStrategy,
}).initialize();

function getRouteHelpers(
Expand Down
10 changes: 5 additions & 5 deletions packages/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export type {
AgnosticNonIndexRouteObject,
AgnosticRouteMatch,
AgnosticRouteObject,
DataStrategyFunction as unstable_DataStrategyFunction,
DataStrategyFunctionArgs as unstable_DataStrategyFunctionArgs,
DataStrategyMatch as unstable_DataStrategyMatch,
DataStrategyResult as unstable_DataStrategyResult,
DataStrategyFunction,
DataStrategyFunctionArgs,
DataStrategyMatch,
DataStrategyResult,
ErrorResponse,
FormEncType,
FormMethod,
Expand All @@ -38,7 +38,7 @@ export type {

export {
AbortedDeferredError,
data as unstable_data,
data,
defer,
generatePath,
getToPathname,
Expand Down
Loading

0 comments on commit 09c1978

Please sign in to comment.