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

Update to React Router v7 #415

Merged
merged 25 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
32be7a3
initial rr7 setup
AlemTuzlak Nov 20, 2024
bd8dbe9
Update README.md
AlemTuzlak Nov 20, 2024
b75279e
Update README.md
AlemTuzlak Nov 20, 2024
a9edefd
Update README.md
AlemTuzlak Nov 20, 2024
58ada89
readme update and pr fixes
AlemTuzlak Nov 20, 2024
404ade3
Update bun.lockb
sergiodxa Nov 22, 2024
59f1c4a
Upgrade dependencies, remove Vitest and setup Happy DOM for Bun Test
sergiodxa Dec 4, 2024
26359d1
Let jsonHash infer return type
sergiodxa Dec 4, 2024
7255b63
Update setupServer to use msw/native
sergiodxa Dec 4, 2024
9dd6e27
Replace .npmignore with files in package.json
sergiodxa Dec 4, 2024
55b92d3
Replace scripts
sergiodxa Dec 4, 2024
a47440b
Don't suggest vitest.explorer extension
sergiodxa Dec 4, 2024
c6a4fb8
Delete Vitest files
sergiodxa Dec 4, 2024
a2f7ff1
Update many tests
sergiodxa Dec 4, 2024
b6647ac
Update bun.lockb
sergiodxa Dec 4, 2024
158b44d
Update TSconfig
sergiodxa Dec 4, 2024
fe742e2
Add bun-vscode extension to VSCode configuration
sergiodxa Dec 4, 2024
cccc0fc
Refactor test structure by moving test files to the src directory and…
sergiodxa Dec 4, 2024
1f7edc6
Remove .history from .gitignore
sergiodxa Dec 4, 2024
1d65301
Remove unused mock-match file and clean up fake-match implementation
sergiodxa Dec 4, 2024
017a9ee
Remove prepare script from package.json
sergiodxa Dec 4, 2024
6baa398
Re-generate bun.lockb
sergiodxa Dec 4, 2024
7dc317f
Refactor CI workflow to simplify triggers and rename job steps
sergiodxa Dec 4, 2024
70cd341
Update test descriptions to use function names and clean up unused va…
sergiodxa Dec 4, 2024
e91678f
Add @testing-library/user-event and update tests to use userEvent for…
sergiodxa Dec 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
/node_modules

*.log
.DS_Store
.DS_Store
.history
sergiodxa marked this conversation as resolved.
Show resolved Hide resolved
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ npm install remix-utils

Additional optional dependencies may be needed, all optional dependencies are:

- `@remix-run/react` (also `@remix-run/router` but you should be using the React one)
- `@remix-run/node` or `@remix-run/cloudflare` or `@remix-run/deno` (actually it's `@remix-run/server-runtime` but you should use one of the others)
- `react-router`
- `@react-router/node` or `@react-router/cloudflare`
sergiodxa marked this conversation as resolved.
Show resolved Hide resolved
- `crypto-js`
- `is-ip`
- `intl-parse-accept-language`
Expand Down Expand Up @@ -427,7 +427,7 @@ First create a new CSRF instance.
```ts
// app/utils/csrf.server.ts
import { CSRF } from "remix-utils/csrf/server";
import { createCookie } from "@remix-run/node"; // or cloudflare/deno
import { createCookie } from "react-router"; // or cloudflare/deno

export const cookie = createCookie("csrf", {
path: "/",
Expand Down Expand Up @@ -494,7 +494,7 @@ Render it in your `root` component and wrap the `Outlet` with it.
When you create a form in some route, you can use the `AuthenticityTokenInput` component to add the authenticity token to the form.

```tsx
import { Form } from "@remix-run/react";
import { Form } from "react-router";
import { AuthenticityTokenInput } from "remix-utils/csrf/react";

export default function Component() {
Expand All @@ -520,7 +520,7 @@ You should only customize the name if you also changed it on `createAuthenticity
If you need to use `useFetcher` (or `useSubmit`) instead of `Form` you can also get the authenticity token with the `useAuthenticityToken` hook.

```tsx
import { useFetcher } from "@remix-run/react";
import { useFetcher } from "react-router";
import { useAuthenticityToken } from "remix-utils/csrf/react";

export function useMarkAsRead() {
Expand Down Expand Up @@ -593,7 +593,7 @@ import { ExistingSearchParams } from "remix-utils/existing-search-params";
```

> **Note**
> This depends on `react` and `@remix-run/react`
> This depends on `react` and `react-router`

When you submit a GET form, the browser will replace all of the search params in the URL with your form data. This component copies existing search params into hidden inputs so they will not be overwritten.

Expand Down Expand Up @@ -632,7 +632,7 @@ By excluding the `page` param, from the search form, the user will return to the
### External Scripts

> **Note**
> This depends on `react`, `@remix-run/react`, and a Remix server runtime.
> This depends on `react`, and `react-router`.

If you need to load different external scripts on certain routes, you can use the `ExternalScripts` component together with the `ExternalScriptsFunction` and `ScriptDescriptor` types.

Expand Down Expand Up @@ -770,7 +770,7 @@ return (
### useGlobalNavigationState

> **Note**
> This depends on `react`, and `@remix-run/react`.
> This depends on `react`, and `react-router`.

This hook allows you to read the value of `transition.state`, every `fetcher.state` in the app, and `revalidator.state`.

Expand Down Expand Up @@ -800,7 +800,7 @@ The return value of `useGlobalNavigationState` can be `"idle"`, `"loading"` or `
### useGlobalPendingState

> **Note**
> This depends on `react`, and `@remix-run/react`.
> This depends on `react`, and `react-router`.

This hook lets you know if the global navigation, if one of any active fetchers is either loading or submitting, or if the revalidator is running.

Expand All @@ -824,7 +824,7 @@ The return value of `useGlobalPendingState` is either `"idle"` or `"pending"`.
### useGlobalSubmittingState

> **Note**
> This depends on `react`, and `@remix-run/react`.
> This depends on `react`, and `react-router`.

This hook lets you know if the global transition or if one of any active fetchers is submitting.

Expand All @@ -844,7 +844,7 @@ The return value of `useGlobalSubmittingState` is either `"idle"` or `"submittin
### useGlobalLoadingState

> **Note**
> This depends on `react`, and `@remix-run/react`.
> This depends on `react`, and `react-router`.

This hook lets you know if the global transition, if one of any active fetchers is loading, or if the revalidator is running

Expand Down Expand Up @@ -922,15 +922,15 @@ The return type of `useLocales` is ready to be used with the Intl API.
### useShouldHydrate

> **Note**
> This depends on `@remix-run/react` and `react`.
> This depends on `react-router` and `react`.

If you are building a Remix application where most routes are static, and you want to avoid loading client-side JS, you can use this hook, plus some conventions, to detect if one or more active routes needs JS and only render the Scripts component in that case.

In your document component, you can call this hook to dynamically render the Scripts component if needed.

```tsx
import type { ReactNode } from "react";
import { Links, LiveReload, Meta, Scripts } from "@remix-run/react";
import { Links, LiveReload, Meta, Scripts } from "react-router";
import { useShouldHydrate } from "remix-utils/use-should-hydrate";

interface DocumentProps {
Expand Down Expand Up @@ -1205,7 +1205,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
Cookie objects in Remix allows any type, the typed cookies from Remix Utils lets you use Zod to parse the cookie values and ensure they conform to a schema.

```ts
import { createCookie } from "@remix-run/node";
import { createCookie } from "react-router";
import { createTypedCookie } from "remix-utils/typed-cookie";
import { z } from "zod";

Expand Down Expand Up @@ -1300,7 +1300,7 @@ await typedCookie.serialize("some fake url to pass schema validation", {
Session objects in Remix allows any type, the typed sessions from Remix Utils lets you use Zod to parse the session data and ensure they conform to a schema.

```ts
import { createCookieSessionStorage } from "@remix-run/node";
import { createCookieSessionStorage } from "react-router";
import { createTypedSessionStorage } from "remix-utils/typed-session";
import { z } from "zod";

Expand Down Expand Up @@ -1775,7 +1775,7 @@ Now you can see in your DevTools that when the user hovers an anchor it will pre
### Debounced Fetcher and Submit

> **Note**
> This depends on `react`, and `@remix-run/react`.
> This depends on `react`, and `react-router`.

`useDebounceFetcher` and `useDebounceSubmit` are wrappers of `useFetcher` and `useSubmit` that add debounce support.

Expand Down
Binary file modified bun.lockb
Binary file not shown.
25 changes: 6 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
"node": ">=18.0.0"
},
"type": "module",
"funding": [
"https://github.com/sponsors/sergiodxa"
],
"funding": ["https://github.com/sponsors/sergiodxa"],
"exports": {
"./package.json": "./package.json",
"./promise": "./build/common/promise.js",
Expand Down Expand Up @@ -95,29 +93,21 @@
"named action"
],
"peerDependencies": {
"@remix-run/cloudflare": "^2.0.0",
"@remix-run/node": "^2.0.0",
"@remix-run/react": "^2.0.0",
"@remix-run/router": "^1.7.2",
"react-router": ">=7.0.0-pre.0",
"crypto-js": "^4.1.1",
"intl-parse-accept-language": "^1.0.0",
"is-ip": "^5.0.1",
"react": "^18.0.0",
"zod": "^3.22.4"
},
"peerDependenciesMeta": {
"@remix-run/cloudflare": {
"optional": true
},
"@remix-run/node": {
"optional": true
},
"@remix-run/react": {
"@react-router/cloudflare": {
sergiodxa marked this conversation as resolved.
Show resolved Hide resolved
"optional": true
},
"@remix-run/router": {
"react-router": {
"optional": true
},

"crypto-js": {
"optional": true
},
Expand All @@ -137,10 +127,7 @@
"devDependencies": {
"@arethetypeswrong/cli": "^0.15.0",
"@biomejs/biome": "^1.7.2",
"@remix-run/node": "^2.0.0",
"@remix-run/react": "^2.0.0",
"@remix-run/router": "^1.7.2",
"@remix-run/testing": "^2.0.0",
"react-router": "7.0.0-pre.6",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^15.0.2",
"@types/bun": "^1.1.1",
Expand Down
19 changes: 13 additions & 6 deletions src/client/cache-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,19 @@ export async function cacheAssets({
function getFilePaths() {
try {
return unique([
...Object.values(window.__remixManifest.routes).flatMap((route) => {
return [route.module, ...(route.imports ?? [])];
}),
window.__remixManifest.url,
window.__remixManifest.entry.module,
...window.__remixManifest.entry.imports,
// biome-ignore lint/suspicious/noExplicitAny: global window type is missing proper typing
...Object.values((window as any).__reactRouterManifest.routes).flatMap(
// biome-ignore lint/suspicious/noExplicitAny: global window type is missing proper typing
(route: any) => {
return [route.module, ...(route.imports ?? [])];
},
),
// biome-ignore lint/suspicious/noExplicitAny: global window type is missing proper typing
(window as any).__reactRouterManifest.url,
// biome-ignore lint/suspicious/noExplicitAny: global window type is missing proper typing
(window as any).__reactRouterManifest.entry.module,
// biome-ignore lint/suspicious/noExplicitAny: global window type is missing proper typing
...(window as any).__reactRouterManifest.entry.imports,
]);
} catch {
throw new Error("Failed to get file paths from Remix manifest");
Expand Down
2 changes: 1 addition & 1 deletion src/react/existing-search-params.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useSearchParams } from "@remix-run/react";
import * as React from "react";
import { useSearchParams } from "react-router";

type Props = {
/**
Expand Down
2 changes: 1 addition & 1 deletion src/react/external-scripts.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useLocation, useMatches } from "@remix-run/react";
import * as React from "react";
import { useLocation, useMatches } from "react-router";
import { HandleConventionArguments } from "./handle-conventions.js";
import { useHydrated } from "./use-hydrated.js";

Expand Down
4 changes: 2 additions & 2 deletions src/react/fetcher-type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type FetcherWithComponents, useNavigation } from "@remix-run/react";
import { type Navigation } from "@remix-run/router";
import { type Navigation } from "react-router";
import { type FetcherWithComponents, useNavigation } from "react-router";

/**
* The list of types a fetcher can have
Expand Down
4 changes: 2 additions & 2 deletions src/react/handle-conventions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Location, Params, useMatches } from "@remix-run/react";
import type { RouterState } from "@remix-run/router";
import type { RouterState } from "react-router";
import type { Location, Params, useMatches } from "react-router";

export type HandleConventionArguments<Data = unknown> = {
id: string;
Expand Down
6 changes: 3 additions & 3 deletions src/react/use-debounce-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useCallback, useEffect, useRef } from "react";
import type {
FetcherWithComponents,
SubmitFunction,
SubmitOptions,
} from "@remix-run/react";
import { useFetcher } from "@remix-run/react";
import { useCallback, useEffect, useRef } from "react";
} from "react-router";
import { useFetcher } from "react-router";

type SubmitTarget = Parameters<SubmitFunction>["0"];

Expand Down
4 changes: 2 additions & 2 deletions src/react/use-debounce-submit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SubmitFunction, SubmitOptions } from "@remix-run/react";
import { useSubmit } from "@remix-run/react";
import { useCallback, useEffect, useRef } from "react";
import type { SubmitFunction, SubmitOptions } from "react-router";
import { useSubmit } from "react-router";

type SubmitTarget = Parameters<SubmitFunction>["0"];

Expand Down
2 changes: 1 addition & 1 deletion src/react/use-delegated-anchors.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PrefetchPageLinks, useNavigate } from "@remix-run/react";
import * as React from "react";
import { PrefetchPageLinks, useNavigate } from "react-router";

const context = React.createContext(false);

Expand Down
2 changes: 1 addition & 1 deletion src/react/use-global-navigation-state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useFetchers, useNavigation, useRevalidator } from "@remix-run/react";
import { useMemo } from "react";
import { useFetchers, useNavigation, useRevalidator } from "react-router";

/**
* This is a helper hook that returns the state of every fetcher active on
Expand Down
2 changes: 1 addition & 1 deletion src/react/use-locales.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMatches } from "@remix-run/react";
import { useMatches } from "react-router";
import type { Locales } from "../server/get-client-locales.js";

/**
Expand Down
2 changes: 1 addition & 1 deletion src/react/use-should-hydrate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMatches } from "@remix-run/react";
import { useMatches } from "react-router";

/**
* Determine if at least one of the routes is asking to load JS and return a
Expand Down
2 changes: 1 addition & 1 deletion src/server/csrf.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Cookie } from "@remix-run/server-runtime";
import cryptoJS from "crypto-js";
import type { Cookie } from "react-router";
import { getHeaders } from "./get-headers.js";

export type CSRFErrorCode =
Expand Down
3 changes: 3 additions & 0 deletions src/server/is-prefetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export function isPrefetch(request: Request): boolean;
export function isPrefetch(headers: Headers): boolean;
export function isPrefetch(requestOrHeaders: Request | Headers): boolean {
let headers = getHeaders(requestOrHeaders);
if (!headers) {
return false;
}
let purpose =
headers.get("Purpose") ||
headers.get("X-Purpose") ||
Expand Down
8 changes: 4 additions & 4 deletions src/server/json-hash.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TypedResponse } from "@remix-run/server-runtime";
import { data, UNSAFE_DataWithResponseInit } from "react-router";

import { json as remixJson } from "@remix-run/server-runtime";

type ResponseResult<LoaderData> = {
[Key in keyof LoaderData]: LoaderData[Key] extends () => infer ReturnValue
Expand All @@ -15,7 +15,7 @@ type ResponseResult<LoaderData> = {
export async function jsonHash<LoaderData extends Record<string, unknown>>(
input: LoaderData,
init?: ResponseInit | number,
): Promise<TypedResponse<ResponseResult<LoaderData>>> {
): Promise<UNSAFE_DataWithResponseInit<ResponseResult<LoaderData>>> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be left for TS to infer it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree, especially now that TypedResponse got nuked out of existence

let result: ResponseResult<LoaderData> = {} as ResponseResult<LoaderData>;

let resolvedResults = await Promise.all(
Expand All @@ -31,5 +31,5 @@ export async function jsonHash<LoaderData extends Record<string, unknown>>(
value as unknown as ResponseResult<LoaderData>[keyof LoaderData];
}

return remixJson<ResponseResult<LoaderData>>(result, init);
return data<ResponseResult<LoaderData>>(result, init);
}
Loading