orval
orval copied to clipboard
TypeScript: Property 'data' does not exist on type 'Validation error'
Hi. I tried to update to version 7.6.0, because I need a fix for safari https://github.com/orval-labs/orval/pull/1918 The problem is that I already have a project in production, and when I update the version I get a lot of typescript errors. For example, in the line:
const selectedFormat = stands.data?.data?.data?.formats?.find((stand) => stand.in_cart);
Error "Property 'data' does not exist on type 'Validation Error | GetAdvertisingDistrictFormats200'.
Property 'data' does not exist on type 'Validation error'
and so on throughout the project, since I did not take this into account during development. I understand that this can be fixed with type guard , but I just do not want to do this, since there are a lot of changes. I found that this error starts appearing with version 7.4.0, but I did not really understand which PR is responsible for this. Can you help me figure out this problem? I can also provide the generated hooks.
this is two different gen:
export function useGetAdvertisingDistrictFormats<
TData = Awaited<ReturnType<typeof getAdvertisingDistrictFormats>>,
TError = ValidationError
>(
districtId: number,
params: GetAdvertisingDistrictFormatsParams,
options?: {
query?: Partial<
UseQueryOptions<Awaited<ReturnType<typeof getAdvertisingDistrictFormats>>, TError, TData>
> &
Pick<
UndefinedInitialDataOptions<
Awaited<ReturnType<typeof getAdvertisingDistrictFormats>>,
TError,
TData
>,
'initialData'
>;
request?: SecondParameter<typeof customFetch>;
}
): UseQueryResult<TData, TError> & { queryKey: QueryKey };
export function useGetAdvertisingDistrictFormats<
TData = Awaited<ReturnType<typeof getAdvertisingDistrictFormats>>,
TError = ValidationError
>(
districtId: number,
params: GetAdvertisingDistrictFormatsParams,
options?: {
query?: Partial<
UseQueryOptions<Awaited<ReturnType<typeof getAdvertisingDistrictFormats>>, TError, TData>
> &
Pick<
UndefinedInitialDataOptions<
Awaited<ReturnType<typeof getAdvertisingDistrictFormats>>,
TError,
Awaited<ReturnType<typeof getAdvertisingDistrictFormats>>
>,
'initialData'
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> };
this is my orval config:
import { defineConfig } from 'orval';
export default defineConfig({
elevators: {
output: {
mode: 'tags-split',
target: './src/api/_gen/react-query/api.ts',
schemas: './src/api/_gen/react-query/models',
client: 'react-query',
clean: true,
httpClient: 'fetch',
baseUrl: `/api`,
mock: false,
prettier: true,
override: {
mutator: {
path: './custom-fetch.ts',
name: 'customFetch'
},
formData: {
path: './fd.ts',
name: 'customFormDataFn'
}
}
},
input: {
target: `/docs/api-docs.json`
},
hooks: {
afterAllFilesWrite: 'prettier --write'
}
},
zod: {
output: {
target: './src/api/_gen/zod/zod.ts',
client: 'zod',
clean: true
},
input: {
target: '/docs/api-docs.json'
},
hooks: {
afterAllFilesWrite: 'prettier --write'
}
}
});
this is my custom fetch:
import { redirect } from 'next/navigation';
import { env } from '@/env.mjs';
// NOTE: Supports cases where `content-type` is other than `json`
const getBody = <T>(c: Response | Request): Promise<T> => {
const contentType = c.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return c.json();
}
if (contentType && contentType.includes('application/pdf')) {
return c.blob() as Promise<T>;
}
return c.text() as Promise<T>;
};
// NOTE: Update just base url
const getUrl = (contextUrl: string): string => {
const url = new URL(contextUrl);
const pathname = url.pathname;
const search = url.search;
const baseUrl = env.NEXT_PUBLIC_API_URL;
const requestUrl = new URL(`${baseUrl}${pathname}${search}`);
return requestUrl.toString();
};
// NOTE: Add headers
const getHeaders = (headers?: HeadersInit): HeadersInit => {
return {
...headers,
'Content-Type': 'application/json',
Accept: 'application/json'
};
};
export const customFetch = async <T>(url: string, options: RequestInit): Promise<T> => {
const requestUrl = getUrl(url);
const requestHeaders = getHeaders(options.headers);
const requestInit: RequestInit = {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...requestHeaders
},
...options
};
const request = new Request(requestUrl, requestInit);
const response = await fetch(request);
const data = await getBody<T>(response);
if (response.status === 401) {
redirect('/auth/logout');
}
return { status: response.status, data } as T;
};
if you need something more, just tell me. thanks a lot
Have you tried 7.9.0 this bug might already be fixed?
Have you tried 7.9.0 this bug might already be fixed?
yes, i also tried 7.9.0, same result, i dont think this is bug...
this is just typescript error. I just thought that upgrading to a new version would force me to change half of my codebase.
That is, I understand that either data or an error can come from the generated hook, and since the typescript does not know what exactly can come, it gives me the error that data is not in the ValidationError type, and this is true, but in version 7.2.0, I do not have this error.
for example in ver 7.2.0:
const hook = useGeneratedHook()
// UseQueryResult<getGeneratedHookResponse, ValidationError> & { queryKey: QueryKey;}
in 7.9,0 ver i have:
const hook = useGeneratedHook()
/* UseQueryResult<getGeneratedHookResponse, ValidationError> & {
queryKey: DataTag<QueryKey, getGeneratedHookResponse>;
*/
}
and alos in 7.2.0 i have Data | undefined in 7.4.0> i have ValidationError | Data
This is my api docs json
{
"/advertising/districts/{district_id}/formats": {
"get": {
"tags": ["advertising/districts"],
"operationId": "GetAdvertisingDistrictFormats",
"parameters": [
{
"$ref": "#/components/parameters/CartHashHeader"
},
{
"name": "district_id",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"minimum": 1,
"example": 1
}
},
{
"name": "from",
"in": "query",
"required": true,
"schema": {
"type": "string",
"format": "date-time",
"example": "2024-08-01T00:00:00.000Z"
}
},
{
"name": "to",
"in": "query",
"required": true,
"schema": {
"type": "string",
"format": "date-time",
"example": "2024-08-31T00:00:00.000Z"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"required": ["data"],
"properties": {
"data": {
"required": ["formats", "advertising_stands_count"],
"properties": {
"formats": {
"type": "array",
"items": {
"required": [
"id",
"key",
"name",
"is_rented",
"in_cart"
],
"properties": {
"id": {
"type": "integer",
"example": 1
},
"key": {
"type": "integer",
"example": 111
},
"name": {
"type": "string",
"example": "A6"
},
"is_rented": {
"type": "boolean",
"example": false
},
"in_cart": {
"type": "boolean",
"example": false
}
},
"type": "object"
}
},
"advertising_stands_count": {
"type": "integer",
"example": 15
}
},
"type": "object"
}
},
"type": "object"
}
}
}
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
}
}
}
}
}
@melloware i also made minimal example https://github.com/Poylar/orval-example
steps to reproduce:
- npm i & npm run gen
- npm run dev
- go to 'pets.tsx' file
- watch on 'bar' variable, no errors
- go to package.json and change orval version in 'gen' script to 7.9.0
- run npm run gen again
- watch on 'bar' variable, you have error
Property 'data' does not exist on type 'ValidationError | GetAdvertisingDistrictFormats200'. Property 'data' does not exist on type 'ValidationError'.
That is weird that it works becuase looking at your generated code ValidationError definitely doesn't have data
export interface ValidationError {
message: string;
errors: ValidationErrorErrors;
}
so to me the TypeScript error is correct.
The issue is this in 7.2.0 we only generated resposnes for 200 OK. Now we generate all valid responses on your code also has a 422 the response is saying it could be EITHER of these responses. IF its a 422 error then your ValidationError definitely does NOT have data on it. So Orval 7.16.0 is generating the correct code.
export type getAdvertisingDistrictFormatsResponse200 = {
data: GetAdvertisingDistrictFormats200;
status: 200;
};
export type getAdvertisingDistrictFormatsResponse422 = {
data: ValidationError;
status: 422;
};
export type getAdvertisingDistrictFormatsResponseSuccess =
getAdvertisingDistrictFormatsResponse200 & {
headers: Headers;
};
export type getAdvertisingDistrictFormatsResponseError =
getAdvertisingDistrictFormatsResponse422 & {
headers: Headers;
};
export type getAdvertisingDistrictFormatsResponse =
| getAdvertisingDistrictFormatsResponseSuccess
| getAdvertisingDistrictFormatsResponseError;