Open kwiat1990 opened 1 year ago
All the functions in remix-typedjson
expects the generic type to be the return type of either a loader or action. This is so it can unwrap the promise and infer the value returned from the json
function.
Is there a reason you're using <SearchResponse | ErrorResponse>
instead of <typeof loader>
?
All the functions in
remix-typedjson
expects the generic type to be the return type of either a loader or action. This is so it can unwrap the promise and infer the value returned from thejson
function.Is there a reason you're using
<SearchResponse | ErrorResponse>
instead of<typeof loader>
?
I didn't use this convention to expect a loader type in a component. I rather returned form loader a specific type, which I also expect in a component. In this case SearchResponse | ErrorResponse
. But I have tried to import a loader from my API route (import { loader as searchLoader } from "~/routes/api.search"
) and used it in fetcher instead. This approach doesn't fix the issue though:
Type 'TypedFetcherWithComponents<({ request }: DataFunctionArgs) => Promise<TypedJsonResponse<ErrorResponse> | TypedJsonResponse<SearchResults>>>' is not assignable to type 'FetcherWithComponents<any> | undefined'.
Type 'TypedFetcherWithComponents<({ request }: DataFunctionArgs) => Promise<TypedJsonResponse<ErrorResponse> | TypedJsonResponse<SearchResults>>>' is not assignable to type '{ state: "idle"; type: "done"; formMethod: undefined; formAction: undefined; formData: undefined; formEncType: undefined; submission: undefined; data: any; } & { Form: ForwardRefExoticComponent<FormProps & RefAttributes<...>>; submit: SubmitFunction; load: (href: string) => void; }'.
Type 'TypedFetcherWithComponents<({ request }: DataFunctionArgs) => Promise<TypedJsonResponse<ErrorResponse> | TypedJsonResponse<SearchResults>>>' is not assignable to type '{ state: "idle"; type: "done"; formMethod: undefined; formAction: undefined; formData: undefined; formEncType: undefined; submission: undefined; data: any; }'.
Types of property 'state' are incompatible.
Type '"idle" | "loading" | "submitting"' is not assignable to type '"idle"'.
Type '"loading"' is not assignable to type '"idle"'.ts(2322)
index.d.ts(184, 5): The expected type comes from property 'fetcher' which is declared here on type 'IntrinsicAttributes & { validator: Validator<{ q?: string | undefined; }>; onSubmit?: ((data: { q?: string | undefined; }, event: FormEvent<HTMLFormElement>) => void | Promise<...>) | undefined; ... 5 more ...; disableFocusOnError?: boolean | undefined; } & Omit<...>'
Remember, your loaders are not returning a standard JavaScript object. It's returning a Response with a JSON payload. The typeof loader
is doing some TypeScript magic to unwrap all that to get at your actual result.
Please provide a full example of what you're trying to do. Here's a sample that works.
import { type LoaderArgs } from '@remix-run/node';
import { typedjson, useTypedFetcher } from 'remix-typedjson';
type SuccessResponse = {
success: true;
results: string[];
};
type ErrorResponse = {
success: false;
error: string;
};
export async function loader({ request }: LoaderArgs) {
const { searchParams } = new URL(request.url);
if (!searchParams.has('query')) {
const result: ErrorResponse = {
success: false,
error: 'No query provided',
};
return typedjson(result);
}
const result: SuccessResponse = {
success: true,
results: ['a', 'b', 'c'],
};
return typedjson(result);
}
export default function Component() {
const fetcher = useTypedFetcher<typeof loader>();
if (fetcher.data) {
if (fetcher.data.success) {
console.log(fetcher.data.results);
} else {
console.error(fetcher.data.error);
}
}
}
The a bit simplified version of my code is available as gist here. In my case fetcher.data
doesn't seem to be typed at all. The fetcher
itself holds following type: const fetcher: TypedFetcherWithComponents<({ request }: DataFunctionArgs) => Promise<TypedJsonResponse<ErrorResponse> | TypedJsonResponse<SearchResults>>>
.
Accessing hits
from successful response yields (and so on for other fields):
Property 'hits' does not exist on type 'ErrorResponse | SearchResults'.
Property 'hits' does not exist on type 'ErrorResponse'.`
Hmm.. it's working for me
import type { LoaderArgs } from '@remix-run/node';
import { typedjson, useTypedFetcher } from 'remix-typedjson';
interface ErrorResponse {
message: string;
}
interface SearchResults {
query: string;
hits: string[];
}
export async function loader({ request }: LoaderArgs) {
const url = new URL(request.url);
const searchTerm = url.searchParams.get('q');
if (!searchTerm) {
return typedjson<ErrorResponse>({ message: 'No query provided' }, 400);
}
try {
// let's pretend it's a result of a fetch call
const result: SearchResults = {
query: 'lorem ipsum',
hits: [],
};
return typedjson(result);
} catch (e) {
return typedjson<ErrorResponse>(
{ message: 'Ooops, something went wrong.' },
{ status: 500 },
);
}
}
export default function Component() {
const fetcher = useTypedFetcher<typeof loader>();
return (
<div>
{fetcher.data && 'message' in fetcher.data && (
<p>{fetcher.data?.message}</p>
)}
{fetcher.data && 'hits' in fetcher.data && (
<>
{fetcher.data.hits.map((hit, index) => (
<p key={index}>{hit}</p>
))}
</>
)}
</div>
);
}
Hm, it seems to work in template but if fetcher.data
is accessed below definition of fetcher const, Typescript will complain with the mentioned error:
if (fetcher.data) {
if (fetcher.data.hits) {
console.log(fetcher.data.hits);
}
}
It also doesn't solve the problem of passing fetcher to ValidatedForm
. You should also be able to see this error, don't you?
if (fetcher.data) {
if (fetcher.data.hits) {
console.log(fetcher.data.hits);
}
}
You haven't narrowed the type, so TS can't resolve data.hits
. In my example, I used a type discriminator: success: true | false
. Your other sample had 'hits' in fetcher.data
As for passing fetcher
to <ValidatedForm/>
, I imagine it's expecting a standard fetcher and not one from typedjson. You'll probably need to use as FetcherWithComponents<T>
I use the packages all over the place in my codebase and today I wanted to take advantage of typed fetcher. I use Remix Validated Form for all forms. It gives a possibility to pass a
fetcher
instance to a form. With the defaultuseFetcher
from Remix there is no problem but as soon as I change it touseTypedFetcher
Typescript starts to complain about incompatibilities between types.Error message:
It's unclear for me if this is something I can/should fix on my own or is related to
Remix Validated Form
or rather toremix-typedjson
?