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

feat(@remix-run/cloudflare,@remix-run/deno,@remix-run/node): SerializeFrom utility for loader and action type inference #4013

Merged
merged 19 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2adec26
docs(@remix-run/react): reunite `useLoaderData` with its jsdoc
pcattori Aug 17, 2022
de502a5
refactor(@remix-run/react): factor out `SerializeType`
pcattori Aug 17, 2022
6e54352
refactor(@remix-run/react): singularize union types
pcattori Aug 17, 2022
d7aaac6
refactor(@remix-run/react): properly constrain objects in typescript
pcattori Aug 17, 2022
6a6f624
refactor(@remix-run/react): remove redundant "Type" suffix from types…
pcattori Aug 17, 2022
275a170
refactor(@remix-run/react): rename `UndefinedOptionals` to `Undefined…
pcattori Aug 17, 2022
665e0db
refactor(@remix-run/react): factor out tuple serialization into type …
pcattori Aug 17, 2022
a6cd1c5
fix(@remix-run/react): `Serialize<any>` should return `any`
pcattori Aug 17, 2022
f6de023
fix(@remix-run/react): serialize classes
pcattori Aug 17, 2022
978ab3d
style(@remix-run/react): do not auto-format typescript ternaries
pcattori Aug 17, 2022
86e6d52
refactor(@remix-run/react): move serialize type utilities into their …
pcattori Aug 17, 2022
a14846c
refactor(@remix-run/react): inline `DataOrFunction` type since its on…
pcattori Aug 17, 2022
8cdedf4
refactor(@remix-run/react): rename `UseDataFunctionReturn` to `Serial…
pcattori Aug 17, 2022
f72d48e
chore(lint): ignore eslint warning about unused typescript generic th…
pcattori Aug 17, 2022
72e0c93
docs(@remix-run/react): jsdoc for `SerializeFrom`
pcattori Aug 17, 2022
439866c
chore(@remix-run/react): add comment explaining `IsAny` implementation
pcattori Aug 17, 2022
638fd10
refactor(serialize): move serialize type utilities into `@remix-run/s…
pcattori Aug 18, 2022
b97e972
feat(@remix-run/deno,@remix-run/cloudflare,@remix-run/node): export `…
pcattori Aug 18, 2022
65aceb4
Create neat-beds-unite.md
pcattori Aug 18, 2022
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
Prev Previous commit
Next Next commit
refactor(@remix-run/react): rename UseDataFunctionReturn to `Serial…
…izeFrom`

since the type can be used without `useLoaderData` or `useActionData` and just returns the JSON
serialized data from a loader or action
  • Loading branch information
pcattori committed Aug 18, 2022
commit 8cdedf49f1bf472c0ddfdf2688fda677c8961f20
35 changes: 19 additions & 16 deletions packages/remix-react/__tests__/hook-types-test.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,81 @@
import type { TypedResponse, UseDataFunctionReturn } from "../serialize";
import type { TypedResponse } from "../serialize";
import type { useLoaderData } from "../components";

function isEqual<A, B>(
arg: A extends B ? (B extends A ? true : false) : false
): void {}

type LoaderData<T> = ReturnType<typeof useLoaderData<T>>

describe("useLoaderData", () => {
it("supports plain data type", () => {
type AppData = { hello: string };
type response = UseDataFunctionReturn<AppData>;
type response = LoaderData<AppData>;
isEqual<response, { hello: string }>(true);
});

it("supports plain Response", () => {
type Loader = (args: any) => Response;
type response = UseDataFunctionReturn<Loader>;
type response = LoaderData<Loader>;
isEqual<response, any>(true);
});

it("infers type regardless of redirect", () => {
type Loader = (
args: any
) => TypedResponse<{ id: string }> | TypedResponse<never>;
type response = UseDataFunctionReturn<Loader>;
type response = LoaderData<Loader>;
isEqual<response, { id: string }>(true);
});

it("supports Response-returning loader", () => {
type Loader = (args: any) => TypedResponse<{ hello: string }>;
type response = UseDataFunctionReturn<Loader>;
type response = LoaderData<Loader>;
isEqual<response, { hello: string }>(true);
});

it("supports async Response-returning loader", () => {
type Loader = (args: any) => Promise<TypedResponse<{ hello: string }>>;
type response = UseDataFunctionReturn<Loader>;
type response = LoaderData<Loader>;
isEqual<response, { hello: string }>(true);
});

it("supports data-returning loader", () => {
type Loader = (args: any) => { hello: string };
type response = UseDataFunctionReturn<Loader>;
type response = LoaderData<Loader>;
isEqual<response, { hello: string }>(true);
});

it("supports async data-returning loader", () => {
type Loader = (args: any) => Promise<{ hello: string }>;
type response = UseDataFunctionReturn<Loader>;
type response = LoaderData<Loader>;
isEqual<response, { hello: string }>(true);
});
});

describe("type serializer", () => {
it("converts Date to string", () => {
type AppData = { hello: Date };
type response = UseDataFunctionReturn<AppData>;
type response = LoaderData<AppData>;
isEqual<response, { hello: string }>(true);
});

it("supports custom toJSON", () => {
type AppData = { toJSON(): { data: string[] } };
type response = UseDataFunctionReturn<AppData>;
type response = LoaderData<AppData>;
isEqual<response, { data: string[] }>(true);
});

it("supports recursion", () => {
type AppData = { dob: Date; parent: AppData };
type SerializedAppData = { dob: string; parent: SerializedAppData };
type response = UseDataFunctionReturn<AppData>;
type response = LoaderData<AppData>;
isEqual<response, SerializedAppData>(true);
});

it("supports tuples and arrays", () => {
type AppData = { arr: Date[]; tuple: [string, number, Date]; empty: [] };
type response = UseDataFunctionReturn<AppData>;
type response = LoaderData<AppData>;
isEqual<
response,
{ arr: string[]; tuple: [string, number, string]; empty: [] }
Expand All @@ -81,13 +84,13 @@ describe("type serializer", () => {

it("transforms unserializables to null in arrays", () => {
type AppData = [Function, symbol, undefined];
type response = UseDataFunctionReturn<AppData>;
type response = LoaderData<AppData>;
isEqual<response, [null, null, null]>(true);
});

it("transforms unserializables to never in objects", () => {
type AppData = { arg1: Function; arg2: symbol; arg3: undefined };
type response = UseDataFunctionReturn<AppData>;
type response = LoaderData<AppData>;
isEqual<response, {}>(true);
});

Expand All @@ -97,7 +100,7 @@ describe("type serializer", () => {
speak: () => string;
}
type Loader = (args: any) => TypedResponse<Test>;
type response = UseDataFunctionReturn<Loader>;
type response = LoaderData<Loader>;
isEqual<response, { arg: string }>(true);
});

Expand All @@ -107,7 +110,7 @@ describe("type serializer", () => {
arg2: number | undefined;
arg3: undefined;
};
type response = UseDataFunctionReturn<AppData>;
type response = LoaderData<AppData>;
isEqual<response, { arg1: string; arg2?: number }>(true);
});
});
8 changes: 3 additions & 5 deletions packages/remix-react/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import type {
Fetcher,
Submission,
} from "./transition";
import type { UseDataFunctionReturn } from "./serialize";
import type { SerializeFrom } from "./serialize";

////////////////////////////////////////////////////////////////////////////////
// RemixEntry
Expand Down Expand Up @@ -1361,7 +1361,7 @@ export function useMatches(): RouteMatch[] {
*
* @see https://remix.run/api/remix#useloaderdata
*/
export function useLoaderData<T = AppData>(): UseDataFunctionReturn<T> {
export function useLoaderData<T = AppData>(): SerializeFrom<T> {
return useRemixRouteContext().data;
}

Expand All @@ -1370,9 +1370,7 @@ export function useLoaderData<T = AppData>(): UseDataFunctionReturn<T> {
*
* @see https://remix.run/api/remix#useactiondata
*/
export function useActionData<T = AppData>():
| UseDataFunctionReturn<T>
| undefined {
export function useActionData<T = AppData>(): SerializeFrom<T> | undefined {
let { id: routeId } = useRemixRouteContext();
let { transitionManager } = useRemixEntryContext();
let { actionData } = transitionManager.getState();
Expand Down
15 changes: 7 additions & 8 deletions packages/remix-react/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,10 @@ export type TypedResponse<T extends unknown = unknown> = Response & {

type ArbitraryFunction = (...args: any[]) => unknown;

export type UseDataFunctionReturn<T extends AppData | ArbitraryFunction> =
Serialize<
T extends (...args: any[]) => infer Output
? Awaited<Output> extends TypedResponse<infer U>
? U
: Awaited<Output>
: Awaited<T>
>;
export type SerializeFrom<T extends AppData | ArbitraryFunction> = Serialize<
T extends (...args: any[]) => infer Output
? Awaited<Output> extends TypedResponse<infer U>
? U
: Awaited<Output>
: Awaited<T>
>;