In fp-ts what is the advantage of using Option instead of typescript's optional (question mark) - fp-ts

I am getting started with fp-ts and was wondering what is the advantage of using Option type over typescript's default notion of optional values represented by question mark?

Typescript tells you whether or not you have the value or not with the question mark. However, often you'll want to perform some sort of computation on them and this is when the Option type from fp-ts shines.
For example, let's say we have two items in a shop and they optionally have prices and we want to write a function to get the price of both of them otherwise we return some value that represents "not for sale". First we'll see how we would do this in vanilla typescript
type ShopItem = {
price?:number
}
const getPrice = (item1: ShopItem, item2: ShopItem):number | null => {
const price1 = item1.price;
const price2 = item2.price;
if (price1 && price2) {
return price1 + price2;
}
return null;
}
This has a few problems. First, there's a bug in there because if the price was 0, then the item should be for sale but instead our falsey check would short circuit out and return null. Secondly, it doesn't scale very well. If we wanted to add multiple ShopItems or even other types of items with optional values, we'll need to keep checking whether or not those values were null.
Now let's compare this to the fp-ts example
https://codesandbox.io/s/laughing-wu-pffe3
import { some, none, ap, Option } from "fp-ts/lib/Option";
import { pipe } from 'fp-ts/lib/pipeable'
type ShopItem = {
price:Option<number> //this type will either be a Some<number> or None
}
const getPrice = (item1: ShopItem, item2: ShopItem): Option<number> =>
pipe(
some((a:number) => (b:number) => a + b),
ap(item1.price),
ap(item2.price)
);
The way this works is that we get the item1 price and the item2 price and we feed it into the addition function inside the some. Notice how there aren't any null checks as this has been abstracted away into our None type.

Related

Typescript: Member of union type with incompatible signature when using find on array of objects

I want to check if a value exists in an array of objects.
My array looks something like this:
[
0: {
id: 'unique_obj_id',
item: {
id: 'unique_item_id',
...
},
...
},
1: {...}
]
The objects in the array can be one of many interface types (depending on my api call, here: resource strings represent these interfaces, which are defined somewhere else). But one array will always consist of the same interface types for a given data request.
I'm trying to write a reusable function to check whether given_id exists for any object in the array for obj.item.id.
So far I've managed to write the correct logic but typescript throws some exceptions that I can't seem to figure out. shopApprovalData is a collection of interfaces each following the above object structure accessible by the indices of resource.
export type ApprovalResource = 'str1' | 'str2' | 'str3' | 'str4' | 'str5';
export const checkApprovalItem = (given_id: string, resource: ApprovalResource) => {
const shopApprovalData = useShopApprovals();
if (shopApprovalData && shopApprovalData[resource]) {
const resourceApprovalData = shopApprovalData[resource];
if (resourceApprovalData.find(e => e.item.id === id)) {
return true;
}
}
return false;
}
Typescript shows me that shopApprovalData and itemApprovalData is possibly undefined and that the expression find() is not callable since signatures of members of the union type are not compatible with each other. (Apparently, some removes the error, why?)
What approach should I choose instead to check whether the given_id exists in any object of the array?
Based on your usecase, i create this code sandbox: https://codesandbox.io/s/stackoverflow-answer-ke9drk?file=/src/index.ts
explanation:
Type wont compile, so we need something to mark what the interface of the object.
Also i may confused by your code shopApprovalData[resource] what do you want to achieve here? for example, if resource is str1 shopApprovalData[resource] will always return undefined because no index str1 in Array
I hope it help,
Best Regards

Create a generic post/put/get in typescript react

So I'm trying to learn writing more generic. I have POST/PUT/DELETE/GET for attatchments together with these entity types (there are also alot more entity types that I will need to add in the future).
This code works, and it is indeed more generic than writing 4 different getAttatchment with hardcorded personnel/workoder etc where it is ${apiType} currently. Some progress made atleast.
But how could I rewrite this even better? I would like to have e.g getAttatchments<T>(EntityType<T> .....) but I don't know how I could write it like that. And ofcourse it need to be typesafe so you don't actually pass entities that does not exist.
export enum EntityType {
Personnel = 'personnel',
Workorder = 'workorder',
Asset = 'asset',
AssetBooking = 'assetbooking',
}
async getAttachments(id: string | number, apiType: EntityType) {
return await this.get<AttachmentDetails[]>(
`attachment/${apiType}/${id}/attachment`
)
}
What you have already looks fairly generic but I would forgo the enum and use a union type instead. e.g.
type EntityType = 'personnel'|'workorder'|'asset'|'assetbooking'
type AttachmentDetails= any // whatever
// mock implementation
function get<T>( url:string) {
return ''
}
function getAttachments<T extends EntityType>(id: string | number, apiType: T) {
return get<AttachmentDetails[]>( `attachment/${apiType}/${id}/attachment` )
}
Playground

Is there any way to fetch only city and country from user input on googleplacesautocomplete api using react?

I can not get only city and country as an output from googleautocomplete component using react.
The way to do this would be to specify the types parameter on the Google Places Autocomplete component. However, the limitation there is that you can only really specify one of the options for types:
You may restrict results from a Place Autocomplete request to be of a
certain type by passing a types parameter. The parameter specifies a
type or a type collection, as listed in the supported types below. If
nothing is specified, all types are returned. In general only a single
type is allowed. The exception is that you can safely mix the geocode
and establishment types, but note that this will have the same effect
as specifying no types.
source: https://developers.google.com/places/web-service/autocomplete#place_types
So the best option for your situation would probably be to use the (regions) type collection, but this will include more than just cities and countries such as neighborhoods. Another option would be to use the (cities) type collection which will include only cities.
I also highly recommend the react-geosuggest library if you want something pre-made to accomplish this as you can pass those as parameters and style it yourself.
<Geosuggest
types={['(regions)']}
/>
EDIT
I think I might have misunderstood the question a bit. If you're trying to figure out how to get the street address, city, and country out of the response you get from the Places API, you're going to have to submit an additional request.
Once the user selects the place from the list, you will have to get the place_id out of the original request and submit a Place Details request. One of the fields in the response will be address_component which has this very strange format. I posted an example of what this full format looks like here.
I made a simple converter function that will return the basic components of an address in a manageable format:
// ---------------------------------------------------------------------- //
// https://developers.google.com/maps/documentation/geocoding/intro#Types //
// ---------------------------------------------------------------------- //
// returns:
// {
// address_1
// address_2
// city
// state_code
// zip_code
// country
// }
function convertAddressComponents(addrComp) {
let newAddr = {};
let address_1 = [];
addrComp.forEach((el, i) => {
if (el.types.includes("post_box")) {
address_1.push(el.long_name);
} else if (el.types.includes("street_number")) {
address_1.push(el.long_name);
} else if (el.types.includes("route")) {
address_1.push(el.long_name);
} else if (el.types.includes("subpremise")) {
newAddr.address_2 = el.long_name;
} else if (el.types.includes("locality")) {
newAddr.city = el.long_name;
} else if (el.types.includes("administrative_area_level_1")) {
newAddr.state_code = el.short_name;
} else if (el.types.includes("postal_code")) {
newAddr.zip_code = el.short_name;
} else if (el.types.includes("country")) {
newAddr.country = el.long_name;
}
});
newAddr.address_1 = address_1.join(" ");
return newAddr;
}
Feel free to modify this to suite your needs.

how to compare the query value from url to id using react?

i want to check if the url contains ?query_param if so then get its value and compare that value to an id.
consider the url /path/20?query_parm=2234
and i have to get the param_id and compare it with the item id.
so i do something like below,
handle_location = (path) => {
let opened_item, param_id;
param_id = new
URLSearchParams(this.props.location.search).get('query_param');
if (this.state.items) {
opened_item = this.state.items.find(item => item.id ===
param_id);
}
};
the data structure for items is below,
items = [{
id: 2244;
attributes: something;
}
{
id: 33;
attributes: nothing;
}]
But this gives the opened_item value undefined since item.id is never equal to param_id... because of type being different.
How can i fix this...or is there a better way to find the query_param from url and get its value and use it accordingly to find the item that matches with the query_param value.
Given you understand that both data types are different, you could use avoid using strict equality and leverage type coercion which would work
item.id == param_id
The most efficient way though would be to convert param_id to the appropriate type before comparing e.g.
param_id = parseInt(param_id, 10);
It means one conversion and you can keep the strict equality
You will need to either cast both of the values to the same type(either Number or String) and then perform the comparison or you could use == operator which will try to coerce the types automatically(not recommended). You can also always fall back to some default value if none of the items matched the id.
if (this.state.items) {
opened_item = this.state.items.find(item => item.id ===
param_id) || 'some default value'
}
try this:
const param_id = this.props.match.params.id

Dealing with nested possibly null values in Typescript

I am using GraphQL and auto generated types for my queries with Typescript. It tends to create quite a few nested type | null types. So I need to do quite long checks for non null values. E.G.
data &&
data.getCoach &&
data.getCoach.workouts &&
data.getCoach.workouts.items &&
data.getCoach.workouts.items.map((workout) => {/* do something*/});
I've tried using lodash has helper to check for path existence like
has('getCoach.workouts.items', data) &&
data.getCoach.workouts.items.map((workout) => {/* do something*/});
but ts compiler doesn't seem to understand this.
Is there better way for checking this apart from making my GraphQL endpoint always return the value?
Edit:
Optional chaining has been released in typescript 3.7!
You can now do:
const items = data?.getCoach?.workouts?.items
items && items.map((v) => {
console.log(v)
})
Previous answer:
Just thought I'd mention that optional chaining has made it to stage 2 at TC39!
I don't think there are any neat solutions at the moment, so something like jcalz's answer might have to do for now.
Hopefully TS introduces it early like they did with async/await 🤞and I can update this answer.
TypeScript isn't quite powerful enough to represent what you're trying to do. Ideally, JavaScript and TypeScript would feature a null-coalescing operator and you'd use that. But it doesn't have that.
If you try to strongly type the lodash has method, you'll find that the compiler cannot concatenate string literal types or perform regular expression operations on them, so there's no way for the compiler to take the string 'getCoach.workouts.items' and understand that getCoach will be a key of data, and that workouts will be a key of data.getCoach, etc.
Even without using has, it is tricky to represent the sort of nested type manipulation involved here without running into issues with recursion of the sort that makes the compiler angry or crashy.
The best I can do is: wrap the object in a Proxy (requires ES2015 or later) which always has a value at any key you want to use, and you use a special key name (such as "value") to pull out the actual value of the property, or undefined if there isn't one. Here's the implementation:
type WrapProperties<T, K extends keyof any> = Record<K, T> & { [P in keyof T]-?: WrapProperties<T[P], K> }
function wrapProperties<T>(val: T): WrapProperties<T, "value">;
function wrapProperties<T, K extends keyof any>(val: T, valueProp: K): WrapProperties<T, K>;
function wrapProperties(val: any, valueProp: keyof any = "value"): WrapProperties<any, any> {
return new Proxy({}, {
get: (_, p) => p === valueProp ? val : wrapProperties(
(typeof val === 'undefined') || (val === null) ? val : val[p], valueProp
)
});
}
So now, instead of this:
if (data && data.getCoach && data.getCoach.workouts && data.getCoach.workouts.items) {
data.getCoach.workouts.items.map((workout) => {/* do something*/})
}
You should be able to do this:
const wrappedData = wrapProperties(data, "val");
if (wrappedData.getCoach.workouts.items.value) {
wrappedData.getCoach.workouts.items.value.map((workout) => {/* do something*/})
}
I don't know if the implementation is perfect; it seems to work when I try it. As with anything you get from Stack Overflow, your mileage may vary and caveat emptor. Maybe it will help you. Good luck!

Resources