/* #flow */
type optionsType = Array<{id: string | number, name: string}>;
type modelsType = Array<{id: number, name: string}>;
function getOptions(options: optionsType): string {
return options.reduce((a, e) => {
return a + `<option value="${e.id.toString()}">${e.name}</option>`;
}, '');
}
const options: modelsType = [
{id: 1, name: 'punto'},
{id: 2, name: 'duo'},
{id: 500, name: 'cinquecento'},
];
console.log(getOptions(options));
The above example complains Cannot call "getOptions" with "options" bound to "options" because number [1] is incompatible with string [2] in property "id" of array element. but in my understanding the modelsType is just more generic than the optionsType. Why does flow complain and how can I get this to work as expected?
If
let second: secondType = first;
were allowed as-is, it would mean that it's valid to do
second.id = "some-id";
but that would corrupt the type of firstType since it's the same object, and the type is number, but it's now been assigned a string.
To make this work, you need to say that secondType.id is read-only, or "covariant". You can do this by changing
type secondType = {id: string | number, name: string};
to
type secondType = {+id: string | number, name: string};
(Example on flow.org/try)
The final solution to my use case:
/* #flow */
type optionsType = $ReadOnlyArray<{+id: string | number, name: string}>;
type modelsType = Array<{id: number, name: string}>;
function getOptions(options: optionsType): string {
return options.reduce((a, e) => {
return a + `<option value="${e.id.toString()}">${e.name}</option>`;
}, '');
}
const options: modelsType = [
{id: 1, name: 'punto'},
{id: 2, name: 'duo'},
{id: 500, name: 'cinquecento'},
];
console.log(getOptions(options));
Related
I am stuck here:
I have an interface Field which has a name and a value of some unknown type.
interface Field<T> {
name: string;
value: T;
}
I then have a function form, which takes any amount of Field's as rest parameters and returns the data they hold.
function form<T extends Field<unknown>[]>(
...fields: T
): { [k in T[number]['name']]: T[number]['value'] } {
let data = {};
fields.forEach((field) => (data[field.name] = field.value));
return <{ [k in T[number]['name']]: T[number]['value'] }>data;
}
It look like this is action:
const age: Field<number> = { name: 'age', value: 30 };
const sex: Field<string> = { name: 'sex', value: 'men' };
const data = form(age, sex);
// { age: 30, sex: 'men' }
In this example the types of age and sex are just the union of all fields.
data.age; // number | string
data.sex; // number | string
What i want is data to be of type:
const data: { age: number, sex: string } = form(age, sex);
But this returns the error ts(2451).
What does the return type of form need to be.
Is this even possible?
(I'm using typescript version 4.9.3, the latest as of 2022-11-29)
[Edit 2022-11-30]: add Link to working example
typescriptlang.org/play
Your Field should have a ganegic on its name also
interface Field<K extends string, V> {
name: K;
value: V;
}
function ensureField<K extends string, V>(field: Field<K, V>) {
return field;
}
type FieldListToRecord<List extends Field<any, any>[], O extends {} = {}> =
| List extends [infer F extends Field<any, any>, ...infer L extends Field<any, any>[]] ?
FieldListToRecord<L, O & (
F extends Field<infer K, infer V> ? { [k in K]: V }
: never
)>
: { [K in keyof O]: O[K] }; // <- Pure<T>
function form<T extends Field<any, any>[]>(
...fields: T
): FieldListToRecord<T> {
return Object.fromEntries(
fields.map(({ name, value }) => [name, value])
);
}
const age = ensureField({ name: 'age', value: 30 });
// ^?
const sex = ensureField({ name: 'sex', value: 'men' });
// ^?
const data = form(age, sex);
// ^?
// { age: 30, sex: 'men' }
Playground
I'm struggling with flowtype declaration for a generic function with different pairs of parameters.
My goal is to have a function which return an object of certain union type depending on input parameters.
I'm having a big load of messages that i want to type (for this example i'm using only two)
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
type MessageFactory<T> = (type: $PropertyType<T, 'type'>, payload: $PropertyType<T, 'payload'>) => T;
export const factory: MessageFactory<Message1> = (type, payload) => {
return {
event: 'UI',
type,
payload
}
}
factory('receive', true);
// factory('send', { foo: true, bar: "bar" });
when i change
MessageFactory<Message1>
to
MessageFactory<Message1 | Message2>
it will throw an error
Could not decide which case to select. Since case 1 [1] may work but if it doesn't case 2 [2] looks promising too. To fix add a type annotation to `payload` [3] or to `type` [4]
You can ty it here
any idea how to declare this function?
or is it stupid idea and i'm going to the wrong direction?
any better solutions?
Create a GenericMessage with type parameters for your desired properties (type and payload), then have your factory return a GenericMessage:
(Try)
type GenericMessage<TYPE: string, PAYLOAD> = {
event: 'UI',
type: TYPE,
payload: PAYLOAD
}
const factory = <T: string, P>(type: T, payload: P): GenericMessage<T, P> => {
return {
event: 'UI',
type,
payload
}
}
const test1 = factory('receive', true);
const test2 = factory('send', { foo: true, bar: "bar" });
// Let's check the new type against Message1 and Message2:
type Message1 = {
event: 'UI',
type: 'receive',
payload: boolean
}
type Message2 ={
event: 'UI',
type: 'send',
payload: {
foo: boolean;
bar: string;
}
}
// Type assertions
(test1: Message1);
(test2: Message2);
(test1: Message2); // Error!
If you want, you can create a MessageFactory type that returns a GenericMessage<T, P>. You can also create an EVENT type parameter if you need to control the event property on the object.
(You don't need to call it GenericMessage, I just called it that to make a distinction between your existing types and this new one)
I see in the normalizer examples that they have a name property "users" being returned on the results object:
{
result: { users: [ 1, 2 ] },
entities: {
users: {
'1': { id: 1 },
'2': { id: 2 }
}
}
}
I can't seem to figure out how to do this with the nested api response I'm getting. I have both a user and an address reducer that I am trying to pass the results of the normalized response into.
I have a JSON response that looks like this:
[
{
id: 1
first_name: First,
last_name: Last,
address: {
data: [
{
id: 1,
address_one: '123 Street Ave',
address_two: '',
city: 'Some City',
state: 'CA',
zip: '1234'
}
]
}
},
{
id: 1
first_name: First,
last_name: Last,
address: {
data: [
{
id: 2,
address_one: '123 Street Ave',
address_two: '',
city: 'Some City',
state: 'CA',
zip: '1234'
},
{
id: 3,
address_one: '321 Avenue Road',
address_two: 'Suite 101',
city: 'Some City',
state: 'CA',
zip: '1234'
}
]
}
}
]
My schema looks like this:
import { schema } from 'normalizr'
/**
* Addresses
*/
const address = new schema.Entity('addresses');
const arrayOfAddresses = new schema.Array(address);
/**
* User
*/
export const user = new schema.Entity('users', {
addresses: arrayOfAddresses
});
user.define({
addresses: {
data: arrayOfAddresses
}
})
export const arrayOfUsers = new schema.Array(user)
Doing: let result = normalize(data, schema.arrayOfUseres)
returns:
{
entities: {
addresses: /* The address data */
users: /* The users with an array of the address ids attached to them*/
},
result: [1, 2]
}
What I would really like is the result object to have both the users and the addresses in it:
{
entities: {
addresses: /* The address data */
users: /* The users with an array of the address ids attached to them */
},
result: {
addresses: [1, 2, 3]
users: [1, 2]
}
}
Is this possible? I've tried several variations of
import * as schema from './schema'
normalize(data, { users: [schema.user], addresses: [schema.address] }
But this just errors out and returns my data back into the result object.
Is this possible?
No. The result value is always set to match the top structure of your input data per the schema. If your input data doesn't have a first-level key addresses, it won't be possible.
I am trying to build a kind of specific simple schema for a collection and i would like to make sure that :
when the user enter a new select in my selectsCollection, he will put one of the options value as selected value.
For example:
SelectsCollection.insert({name:"SelectOne",deviceType:"Select",options:["option1","option2","option3"],value:"option4",description:"This is the first select"});
this do not have to work. I want him to write only one of the 3 options.
Here my schema :
SelectsCollection = new Mongo.Collection('Selects'); //Create a table
SelectsSchema = new SimpleSchema({
name:{
type: String,
label:"Name",
unique:true
},
deviceType:{
type: String,
allowedValues: ['Select'],
label:"Type of Device"
},
options:{
type: [String],
minCount:2,
maxcount:5,
label:"Select Values"
},
value:{
type: String,
//allowedValues:[options] a kind of syntax
// or allowedValues:function(){ // some instructions to retrieve the array of string of the option field ?}
label:"Selected Value"
},
description:{
type: String,
label:"Description"
},
createdAt:{
type: Date,
label:"Created At",
autoValue: function(){
return new Date()
}
}
});
SelectsCollection.attachSchema(SelectsSchema);
Any Idea ? :)
Thanks a lot !
This could be done with custom validation function of a field, inside this function you could retrieve values from other fields:
SelectsSchema = new SimpleSchema({
// ...
options: {
type: [String],
minCount: 2,
maxcount: 5,
label: "Select Values"
},
value: {
label: "Selected Value",
type: String,
optional: true,
custom() {
const options = this.field('options').value
const value = this.value
if (!value) {
return 'required'
}
if (options.indexOf(value) === -1) {
return 'notAllowed'
}
}
},
// ...
});
Look here custom-field-validation for more information
From what I understand in the docs, you can define your schema like this:
MySchema = new SimpleSchema({
//This says that the addresses key is going to contain an array
addresses: {
type: [Object],
},
// To indicate the presence of an array, use a $:
"addresses.$.street": {
type: String,
},
"addresses.$.city": {
type: String,
}
});
Ok, I get this part. But what if I wanted to validate the contents in a specific array index? I want something like this:
MySchema = new SimpleSchema({
//This says that the itemsOrdered key is going to contain an array
itemsOrdered: {
type: [Object],
},
// Here I want to validate certain indexes in the array.
"itemsOrdered.0.sku": {
type: String
},
"itemsOrdered.0.price": {
type: Number
},
"itemsOrdered.1.sku": {
type: String
},
"itemsOrdered.1.price": {
type: Number
},
"itemsOrdered.1.quantity": {
type: Number
},
"itemsOrdered.2.sku": {
type: String
},
"itemsOrdered.2.price": {
type: Number
},
"itemsOrdered.2.customerNotes": {
type: String
optional: true
}
});
So here I'm trying to validate the values inside array index 0, 1, and 2. Each array index has a different item that has been ordered.
Normally I would use a hash table data structure, but for this purpose I need to preserve order which is why I'm using an array.
When I try to run this code I get an error Cannot read property 'blackbox' of undefined
Have you considered custom validation?
https://github.com/aldeed/meteor-simple-schema/blob/master/README.md#custom-validation
According to the doc within the function the key property of this will provide the information you want. So you could have something like:
MySchema = new SimpleSchema({
//This says that the itemsOrdered key is going to contain an array
itemsOrdered: {
type: [Object],
},
// Here I want to validate certain indexes in the array.
"itemsOrdered.$.sku": {
type: String,
custom: function () {
var key = this.key,
re = /\d+/;
var index = Number(key.match(re)[0]);
// Do some custom validation
}
},
"itemsOrdered.$.price": {
type: Number
},
"itemsOrdered.$.quantity": {
type: Number,
optional: true
},
"itemsOrdered.$.customerNotes": {
type: String,
optional: true
}
});
Here I put the validation logic in the sku field since it's required.