how to use react-hook-form setValue in a generic function? - typescript-generics

This is my original function, which I would like to transform to a generic one:
import { FieldValues, useForm, UseFormSetValue } from 'react-hook-form'
type FormData = {
lid: number
lang_id: number,
source_id?: number | string | null
town_module_id?: number | null
region_module_id?: number | null
digest?: string
title?: string
}
const { getValues, setValue, handleSubmit, control,
formState: { errors }} = useForm<FormData>();
function setFormValues(setValue: UseFormSetValue<FormData>, lid: number, data: NewsItem) {
setValue("lid", lid);
setValue("lang_id", data.lang_id);
setValue("town_module_id", data.town_module_id);
setValue("region_module_id", data.region_module_id);
}
The FormData and the NewsItem struct have the lang_id etc. fields, and also the other structs I would like to use it with.
My attempt to make this function generic:
export type BasicFormData = {
lid: number
lang_id: number,
source_id?: number | string | null
town_module_id?: number | null
region_module_id?: number | null
}
static setFormValues<D extends BasicFormData,I extends BasicFormData>(setValue: UseFormSetValue<D>, lid: number, data: I) {
setValue(<Path<D>>"lid", lid);
setValue("lang_id", data.lang_id);
setValue("town_module_id", data.town_module_id);
setValue("region_module_id", data.region_module_id);
}
I tried to put <Path> before "lid" and it is ok for the linter
but, for the lid value the linter says:
Argument of type 'number' is not assignable to parameter of type 'PathValue<D, Path>'.ts(2345)
and:
Argument of type '"lang_id"' is not assignable to parameter of type 'Path'.ts(2345)
How can i resolve this problem?

Related

Presetting values for a type using Generics - Typescript

I know I'm way off track here but I'm trying to use generics to "preset" particular values of an "Event" type.
What I'm doing is as follows and what I'm expecting is "my-source" to be the console output.
I can see why this code doesn't work, but I'm putting it here to try and illustrate what I want to try and achieve.
type EventDetail = {
shopId: string;
};
export type Event<
TEventDetailType extends EventDetail,
TType extends string,
TSource extends string
> = {
source?: TSource;
body: TEventDetailType;
type?: TType;
};
export type UpdateShopCustomDomainEventDetail = EventDetail & {
domainName: string;
};
export type UpdateShopCustomDomainEvent = Event<
UpdateShopCustomDomainEventDetail,
"update-shop-custom-domain",
"my-source"
>;
const test:UpdateShopCustomDomainEvent = {body:{shopId:"123123", domainName:"asdf"}}
console.log(test.source);
// undefined
Essentially what I am wanting to do is something like the following. Where I send an event, but I define the type of event that I'm sending with a generic. This UpdateShopCustomDomainEvent will force the user to enter a value for shopId and domainName, and it will also set the type value to "update-shop-custom-domain" and the source to "my-source".
putEvent<UpdateShopCustomDomainEvent>({
body: {
shopId:"1234",
domainName:"mydomain.com"
}
});

Why can't I use an Intersection type here but spreading one type into another works?

Today I found out that I can do the latter here, but not the former, and I can't figure out why.
This does NOT work:
type User = {| id: number |}
type ExternalUser = User & {| externalUser: boolean |}
checkUser = (user: User | ExternalUser) {
if (user.externalUser) {
} else {
}
}
But this does:
type User = {| id: number |}
type ExternalUser = {| ...User, externalUser: boolean |}
checkUser = (user: User | ExternalUser) {
if (user.externalUser) {
} else {
}
}
In the first example, flow tells me externalUser does not exist in User on the if statement check. What am I missing? Seems like the end result should be the same. Possible Flow bug or am I breaking a rule that I'm not aware of?

Restricting function to object type

I have a function which iterates over an object's properties, like this:
function somef(obj) {
for (const prop in obj) {}
}
This is reported as a possible string by Flowtype:
Cannot iterate using a `for...in` statement because string [1] is not an object, null, or undefined. [invalid-in-rhs]
54| for (const prop in obj) {}
^^^
References:
54| for (const prop in obj) {}
^^^^ [1]
I tried typing obj as any, and this checks, but then it allows passing in a string to somef(), which I understand as string being an any:
function somef(obj:any) {
for (const prop in obj) {} // Would fail at runtime
}
somf("mystring) // Checks
Is it possible to restrict somef to only accept bracket objects obj of the form {} on which I can call: for (prop in obj) {} on?
Your problem is that you have not given any explicit type def to the function so it implicitly defines types based on the code around it, given it's being called with a string, it would assume obj is a string and it can't be looped. It throws an error correctly, just not in the place you expect because of this implicitly typed def.
If you know the exact structure of the object, you can type as
function somef(obj: { a: string, b: string }) {
for (const prop in obj) {}
}
But if it's more unknown I recommend indexer object,
function somef(obj: { [key: string]: any }) {
for (const prop in obj) {} // Would fail at runtime
}
docs: https://flow.org/en/docs/types/objects/
try example

Typing object of functions with flowtype

I understand that this is the type of an object, which keys are the two strings 'foo' and 'bar', and the values two functions (although I find the syntax strange):
type Func0 = {
foo(number) : number,
bar(string) : string
};
for example:
const f: Func0 = {
foo: x => 2*x,
bar: x => `hello ${x}`
};
But what is this type? The type of an object with two functions as values? If so, what are the keys?
type Func1 = {
(number) : number,
(string) : string
}
Yes, this is the callable object with two possible strict outcomes. Repro example here - try flow.
In brief,
type fn = {
(number): number,
(string): string
}
in pseudo-code is:
type fn =
(number) => number
OR
(string) => string
so:
fn(number): number
fn(string): string
While with default function declaration you cannot keep the relation between parameter type and return type:
type fn = (number | string) => number | string
in pseudo-code is:
type fn = (number OR string) => number OR string
so:
fn(number): number | string
fn(string): number | string

Recursively Create ReadOnly Object in FlowJS

In redux, the state should be immutable. I would like Flow to prevent anyone from mutating that state. So, given an object of arbitrary depth:
type object = {
a: {
b: {
d: string
}
},
c: number
}
How can I create a new type that is recursively readonly, so that I cannot do:
let TestFunction = (param: $RecursiveReadOnly<object>) => {
param.a.b.d = 'some string'
}
The builtin $ReadOnly utility of Flow will create a type like this, which isn't what is needed, because b & d are still writable:
{
+a: {
b: {
d: string
}
},
+c: number
}
I've been trying to use the $Call & $ObjMap(i), but I can't figure out how to recursively travel an object in Flow. The objective is to have this:
{
+a: {
+b: {
+d: string
}
},
+c: number
}
Thanks to kalley for his solution. From what I understood, kalley tried to make any object received by a function recursively read only. Since I really only needed known objects as parameters, this works perfectly:
// Type definition that works with arbitrary nested objects/arrays etc.
declare type RecursiveReadOnly<O: Object> = $ReadOnly<$ObjMap<O, typeof makeRecursive>>
declare type RecursiveReadOnlyArray<O: Object> = $ReadOnlyArray<$ReadOnly<$ObjMap<O, typeof makeRecursive>>>
type Recursive<O: Object> = $ObjMap<O, typeof makeRecursive>
declare function makeRecursive<F: Function>(F): F
declare function makeRecursive<A: Object[]>(A): $ReadOnlyArray<$ReadOnly<Recursive<$ElementType<A, number>>>>
declare function makeRecursive<O: Object>(O): RecursiveReadOnly<O>
declare function makeRecursive<I: string[] | boolean[] | number[]>(I): $ReadOnlyArray<$ElementType<I, number>>
declare function makeRecursive<I: string | boolean | number | void | null>(I): I
// Usage example.
type obj = {
a: {
b: {
d: string,
}
}
}
let TestFunction = (param: RecursiveReadOnly<obj>) => {
param.a.b.d = 'some string' // Flow throws an error
}

Resources