Defining Object Literals with Flow - javascript

I am attempting to define this object:
let apiFormatted: {"barcode": number, "id": number} = {"barcode": this_barcode.barcode, "id": this.props.currentJob}
The error I am getting from Flow is:
Flow: object literal. This type is incompatible with object type
I have attempted {[string]: number, [string]: number} as well with no success. I am new to Flow and any help would be appreciated.

First, some words about flow:
flowtype is a typechecker. That means it checks if you use the variables the way you have declared them. This has two advantages:
1) you can find errors while you write your code, and it does not occur when you execute it, which makes your code more bulletproof
2) it makes the code more readable, therefore its easier for others to understand the code and you can work in a team.
The only disadvantage is that you need to type much more. However flow infers much of the types, that means that it checks for the types even if you don't tell it to. Therefore
let obj = {barcode : this_barcode.barcode, id : this.props};
actually infers the type:
{barcode: number, id: number}
That means that the following won't work:
obj.barcode = "fails"
as flow infers a number, but I tried to assign a string.
When you want to pass it to a function and do:
function open(barcode : {}){
console.log(barcode.id);
}
This is dangerous, beause you can do
open({});
And it won't tell you that the code wont work as wanted. So there it makes sense to define id as aproperty:
function open(barcode : { id : number }){
If your object has many propertes that are extensively used, it's probably a good idea to define an Interface once:
interface Barcode {
barcode : number;
id : number
}
let obj : Barcode = {barcode:1, id:2};

Related

Should be type placed on both sides of declaration

With the type MyType being
export type MyType =
{
ID: string,
Name?: string
};
I can go ahead and declare variable myVar using three slightly different syntax:
With MyType next to the variable name:
let myVar: MyType = { ID: "abc" };
With MyType after the value:
let myVar = { ID: "abc" } as MyType;
With MyType next to the variable name and after the value:
let myVar: MyType = { ID: "abc" } as MyType;
Would be any difference in the resulting myVar variable using any of three methods? What should be a preferred way and why?
Using : someType on the left hand side is type annotation. It indicates that the only thing assignable to the variable should be something of that type. When you need to indicate to TS that a variable should contain a particular type, that should be preferred.
You should avoid as, which is type assertion - which basically tells the compiler "I'm sure I know what I'm doing, the expression on the left is really of type X even if you can't infer it." This should be avoided when possible, since it reduces the amount of type-checking TypeScript can do in some circumstances.
That said, often, you don't need either of those: that is, having TypeScript infer myVar to be of type { id: string } will probably work in most circumstances, especially if you aren't reassigning the variable (best to avoid variable reassignment when possible anyway - it's usually simple enough).
So, consider if you can do just
let myVar = { ID: "abc" };
without any typing at all. It'll be safe to use for, eg, arguments that expect something of MyType, but without any type annotation or assertion (less unnecessary type noise often makes code more readable).
In Option 1, you are declaring the variable as of a certain type and assigning it a value. If the value does not fit the type, it will complain at compile time. This is the preferred method as you get advanced warning that something might not match up correctly.
In Option 2, you are declaring a typeless variable and kind of casting the values to a certain type. This can be useful at times but also could give you run time errors, depending on usage (YMMV)
Option 3 is not really used as it's redundant, IMHO.

Override function return type in typescript

Is there a way to override a function's return type? Using typescript 3.1.6 at the time of writing.
Contrived example, but to get the point across:
function sample(foo): string | number {
if (foo === 'foo') {
return 'string'
}
return 1
}
const result = sample('bar')
// but since we know it's a number we should be able to tell typescript this
const typedResult: number = sample('bar')
In reality, the logic in the function might be complex.
keyof and lookup types looked promising, but I'm not working with a limited set of options nor can I deduce a pattern reliably.
I've also tried the following workaround, but it didn't work. It seems like you can't override a type.
const result = sample('bar')
const typedResult: number = result
What's frustrating about this is that even if I do the proper checks to accurately check what type I'm dealing with, I still get type errors if I use methods exclusive to that type. Typescript still thinks we don't 100% know the type.
Any leads or solutions to this?
For that you'd use a type assertion:
const typedResult = sample('bar') as number
That said, it may be worth separating sample into three functions:
One that always returns a number
One that always returns a string
One that does what sample does in your question (having it do the relevant parameter test and then hand off to one of the other two functions)
...so that when you know what type your result will be, you can use one of the first two and not use a type assertion.

Detecting whether a generic type argument has an id property?

I'm designing a generic Slice<E> class which represents a slice of instances of a set of instances. For example if we have Todo instances, then the slice could represent all the ones that are completed.
If the Todo class has an id property we would like to set hasID to true on the Slice instance, such that we can also index the Slice instance by id. Is there a way to detect at runtime whether the generic argument has and id property?
There's not a lot here to go on without code.
On the face of it: no, you cannot possibly analyze a generic type argument at runtime since the type system is erased. At runtime you can only analyze runtime values, such as the actual objects you pass in to the constructor. At compile time you can probably make the compiler give a specific true or false boolean literal type to hasID based on the generic type parameter. And you can do both of those things and hope that you've got a solution where the runtime values actually match up with compile-time types.
Let's try it. Here's a sketch of a possible solution:
class Slice<E> {
instances: E[]
hasID: E extends {id: any} ? true : false;
constructor(firstInstance: E, ...restInstances: E[]) {
this.hasID = 'id' in firstInstance as Slice<E>['hasID'];
this.instances = [firstInstance, ...restInstances];
}
}
The hasID property is given a conditional type that evaluates the E type parameter and returns true if E['id'] exists, and false otherwise.
The constructor accepts at least one parameter of type E, the first one of which is analyzed at runtime for an id property in order to set the hasID runtime value.
Let's see if it works:
const sliceOne = new Slice({a: 1}, {a: 2});
sliceOne.hasID // false at compile time and runtime
const sliceTwo = new Slice({id: 1}, {id: 2});
sliceTwo.hasID // true at compile time and runtime
Looks good. Still there are edge cases you might need to worry about, like:
declare const notSure: object;
const oopsie = new Slice(notSure);
oopsie.hasID; // false at compile time, maybe true at runtime!
The compiler can't verify that object has an id property, so it gives hasID the type false. But of course an object may have an id property, so maybe hasID will be true at runtime. Is there a way to deal with this? Maybe. But it's not straightforward. The question is how likely you are to run into these cases and if you care about them.
Anyway, hope that makes sense and gives you some direction. Good luck!

How to typecheck a generic function using a value of that same generic type in Flow

We use Flow with React, and we wrote this DropDown wrapper that does some things for us.
Until now, this drop down only needed to work with number ids, but recently, we had to add support for string ids as well.
Not wanting to rewrite everything, we thought about using a generic type parameter to our props, so that the ids (and the onChange handler) would use that generic type.
However, we do not seem to be able to use that change handler correctly. When trying to call the handler, we get a Flow error about the type of the value we are sending it (either number or string) telling us that this type is not compatible with T:
Cannot call 'handler' with 'value' bound to 'value' because string [1] is incompatible with 'T'
And same thing with the number call.
It was our belief that if we successfuly check the type of one of our generic values, it would collapse that type for every element using the same generic type in our props. Here they are:
type Props<T : string | number> = {
options?: Array<{id: T, displayValue: string}>,
onValueChange?: (value: T | null) => mixed,
}
Because we know that Flow will not allow to create an element using these props with an onValueChange that would deal with number if the options contain ids with string, for instance, we thought that by checking the type of an id in options, we would collapse the T for the onValueChange as well.
Here is where we get the error:
export class DropDown<T : string | number> extends React.Component<Props<T>, {}> {
handleChange = (event: {target: {value: string}}) => {
if (!this.props.onValueChange) return
const handler = this.props.onValueChange
const firstElement = this.props.options && this.props.options[0]
if (!firstElement) return
const value = event.target.value
// I don't understand why, but this if-else if generates an error.
if (typeof firstElement.id === 'string') {
handler(value)
} else if (typeof firstElement.id === 'number') {
const num = parseInt(value)
handler(num)
}
}
}
As such, my question would be: is it possible to determine the generic type of the function's parameter so that we can use it as we want, without errors?
If the answer is yes, what are we doing wrong in our code that prevents the type check to work as wanted?
If the answer is no, is there a better way than casting our value as any to make our handler accept it?
I have written a minimal working example in this Flow Try snippet, so you can see the live errors, as well as the expected usage (please note that the errors in the usage section are expected: they are there to show that using the generic component works as we want it to, even if using the values doesn't). The errors that matter are the first two in the error output.
Thank you in advance for your help!
Thanks for this very interesting question!
Unfortunately I think that right now it is not possible to refine the type T by refining the type of the first element in the array. This can be seen when we use the following statement:
if (firstElement) {
if (typeof firstElement.id === 'string') {
const refined: string = firstElement.id;
const notRefined: T = refined; // Errors
const alwaysWorks: T = firstElement.id;
}
const alwaysWorks: T = firstElement.id;
}
The second assignment errors. Funnily this can be fixed by labeling also the first assignment as T which shows how Flow works: Flow does not refine types itself, it refines the type label of a value. And I think you have rightfully already concluded that what you need to do is refine the value of the callback function. But this does not work since we cannot do any runtime checks on functions (except maybe get their number of arguments).
So we learn that Flow just isn't good enough. When you get deeper into using Flow you might notice this from time to time: It is just very hard to express what you are trying to do. I am not sure if Flow is at fault or if it is just JavaScript that is very hard to type. Anyways as a solution I want to suggest that you simply annotate the handler variable with Function. Function is somewhat an any type and makes the function more general. We know that the code works (please see my corrections making the null check and then checking the id property instead of the object) and the external interface is very explicit about the types. Sometimes it is the API that matters and internally we have to hack a bit. This is fine (we can for example see this a lot in popular projects like these 13 cases in graphql-js)
Bonus: If you are into functional programming check out how this library plays around with types to create the illusion of a Maybe type that has no runtime overhead since it is really just null under the hood.
I think you are overthinking the problem.. Looks like you have just made a small mistake when declaring the type of event.target.value as a "string" but not "T".
Try this out
export class DropDown<T : string | number> extends React.Component<Props<T>, {}> {
handleChange = (event: {target: {value: T}}) => { // not {value: string}
if (!this.props.onValueChange) return
const handler = this.props.onValueChange
const value = event.target.value
handler(value)
}
}
I hope this will help you.

How do I ensure typescript interface properties are uppercase after I obtain result in angular 5?

I have a web api that returns the following result
{
"customerContactID": 1,
"customerID": 1,
"firstName": "james",
"lastName": "smithman",
"primaryEmail": "james#gmail.comm",
"primaryPhone": "8788494677",
"isPrimaryContact": true
}
and I have an angular 5 app that defines an interface as
interface CustomerContact {
CustomerContactID: number;
CustomerID: number;
FirstName: string;
LastName: string;
PrimaryEmail: string;
PrimaryPhone: string;
IsPrimaryContact: boolean;
}
and return the result using
this.http.get<CustomerContact>(url).subscribe(result => {
console.log("CustomerContact obtained");
console.log(result); // prints lowercase properties
this.customerContact = result;
}, error => console.error(error));
Unfortunately, when i log the result, I can see that the properties have all been lowercased, so I cannot do something such as
this.currentCustomer = result.CustomerID;
Since it results in undefined. However, I need to be able to set a variable value to the value obtained from the api result, specifically result.CustomerID.
Typescript does not allow me to do
this.currentCustomer = result.customerID;
since it results in
TS2551: Property 'customerID' does not exist on type 'CustomerContact'. Did you mean 'CustomerID'?
How do I set the value of a variable to the value of result.customerID despite the compiler [at-loader] error?
I cannot change the API contract at all, also, my typescript interface must have UpperCase for property names.
UPDATE 1
as #pArth savadiya mentioned below, It looks like I can do this
Although, Im not sure if the repercussions, if any
I dont believe this is a duplicate of
Convert returned JSON Object Properties to (lower first) camelCase
since that question has a result model that has uppercase properties, which is not what I have here.
UPDATE 2
After some close observation ,I realized that the big issue here was the MISTMATCH between the api response property casing and the angular/typescript casing mismatch. Without them being the same, it causes issues and forces odd workarounds. The solution simply was to match the interface casing with the response casing for this particular request. Its that simple. Thank you everyone.
In your code you tightly coupled HTTP response result with your typescript interface(CustomerContact) use instead of it.
this.http.get <any> (url).subscribe(result => {
console.log("CustomerContact obtained");
console.log(result); // prints lowercase properties
this.customerContact = result;
}, error => console.error(error));
then you can able to write
this.currentCustomerID = result.customerID;
or you can try like this: this.currentCustomer = result["customerID"];
In comparison with JavaScript, TypeScript helps to make explicit choices about types i.e. about the design of the code. Your question is indeed a matter of design decisions of your Angular application.
It helps to reason considering layers in the architecture: the WebApi, the service fetching its data and the UI components are not in the same layers.
You can decide to have for the “CustomerContact”:
The same type in all layers:
This type is defined by the WebApi so all fields are in camelCase: interface CustomerContact { customerContactID: number… } and you can do http.get<CustomerContact>(url).
It’s cheap in the short term but expensive in the long run because any change in the WebApi signature can induce a lots of changes in several files in all layers!
Distinct types:
The WebApi method returns a DTO: interface CustomerContactDto { customerContactID: number… } and http.get<CustomerContactDto>(url). Don’t use the type any if you want to keep the type safety provided with TypeScript.
The domain layer deals with a CustomerContact with whatever fields you likes, in whatever case you wants (camelCase, PascalCase, …) even if PascalCase in more a C# convention than a TypeScript one.
The service containing the http.get<CustomerContactDto>(url) will do the mapping between both types to return a CustomerContact, as part of an ACL. This mapping can be done simply field by field: const customerContact = new CustomerContact(); customerContact.CustomerContactID = result.customerContactID; ….
In your interface change
CustomerID: number;
to
customerID: number;
It will work as you expect.

Categories