flow nested object values not accesible - reactjs

I'm trying to access deeply nested values from an object but i'm getting the following error in flow:
Property cannot be accessed on property 'author' of unknown type
type ARTICLE_TYPE = {
id: number,
authorId: number,
type: 'article' | 'video' | 'audio' | 'perspective',
title: string,
preview: string,
imageUrl: ?string,
date: string,
}
type AUTHOR_TYPE = {
company: string,
id: number,
name: string,
profileImage: string
}
type TileProps = {
...ARTICLE_TYPE,
...{
author: AUTHOR_TYPE,
},
imageAspectRatio: string
}
I suspect it might be something to do with the way i'm defining the type but it seems ok to me:
Relevant testable code is Here
Any help is appreciated!

I suggest you don't use spread operators with flow types, rather use type concatenation.
type TileProps = ARTICLE_TYPE & {
author: AUTHOR_TYPE,
imageAspectRatio: string
}
Make life easy on yourself and those who read your code after you.

Related

TypeScript File upload error - Type 'File' is not assignable to type 'string'

This is my PlayerInterface
interface playerInterface {
id?: string,
_id?: null,
name: string
club: string,
image?: string,
important: boolean
}
This is my useState hook:
const [player, setPlayer] = useState<PlayerInterface>({ id: '', name: '', club: '', important: false, image: '' })
And this is my handleFileUpload:
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
setPlayer({ ...player, image: e.target.files![0] })
}
Right now, in handleFileUpload, the image is underlined, with the error saying Type 'File' is not assignable to type 'string'
What is the solution for this?
Edit
Based on your edit to playerInterface, you are trying to assign a File ( e.target.files![0]) to a string variable. Try changing playerInterface to a File type, or using a ReadableStream to store it as a data string.
Without knowing what database you are using, it is difficult to say exactly what the problem is, but it is likely you will need to convert the binary/Base64 string representation of the uploaded file back into its original format in order to store it as a File. However, from my experience, I would suggest storing the file as the string representation, and only converting to the image/file representation when it is actually required (i.e. on rendering in your front end).

TypeScript - How to infer a type from array of objects

I am building a React form component with TypeScript and want to type the data returned by my form.
My component accepts a prop called "fields" with the following structure:
const fields = [
{
name: "title",
default: "",
data: undefined,
size: 2
},
{
name: "users",
default: [],
data: [{id: 1, ...}]
size: 8
},
]
I would like to find a way to retrieve the type of the data returned by component based on the "field" variable. So, basically, I want to get something like that:
type FormData = {
title: string;
users: Array<{id: number, ...}>
}
The best solution would be to infer a different type depending on the "data" key. If "data" exists, then the field will have the same type as data, otherwise it is the type of the "default" key. All other keys should be ignored.
I believe I should be using generics to achieve this but I am not even sure this is something possible with TS and I have to admit that I can't figure it out...
Has anyone already faced this situation and found a solution to a similar issue ?
Thank you very much!
Based on the given array fields, we can create the type FormData with the following generic type as long as the fields variable was initialized with as const to stop TypeScript from widening the string literals.
const fields = [
{
name: "title",
default: "",
data: undefined,
size: 2
},
{
name: "users",
default: [],
data: [{id: 1}],
size: 8
},
] as const
type FormData<T extends readonly { name: st
[E in T[number] as E["name"]]: E["data"]
? E["default"]
: E["data"]
}
type Result = FormData<typeof fields>
// type Result = {
// title: "";
// users: readonly [{
// readonly id: 1;
// }];
// }
This might or might not work in the context of your component. But you did not show us the component itself. This is a solution based on the information given in your question.
Playground

How to infer types of an object created from a schema?

I'm trying to implement something similar to storybook's "Controls" feature where you can define a bunch of properties, and they become controls in the UI.
I defined a schema type and a schema of how to create those controls:
// Example schema
var newSchema: BlockSchema = {
title: "New Schema",
controls: {
name: {
type: 'string',
placeholder: 'Please insert your name'
},
size: {
type: 'select',
options: ['quarter', 'half', 'full']
},
hasInfo: {
type: 'bool'
},
amount: {
type: 'number'
}
}
}
But now I need a type that is the result of what the user has selected. A type for the final values, something like:
type MapControlTypes = {
bool: boolean;
string: string;
select: string;
number: number;
};
type InferType<T extends BlockSchema> = { /* MapControlTypes<?????????> */ }
type NewSchemaControls = InferType<typeof newSchema>;
/* Expected result:
NewSchemaControls = {
name: string;
size: string;
hasInfo: boolean;
amount: number;
}
*/
I need to infer the types from the controls property of my schema, but how could I implement this inference? Here's a playground with complete example code
I tried implementing this, and this solution. But they don't work well and also only support two types.
Titian Cernicova-Dragomir's solution didn't work too. Playground, but it has a very similar problem that happened when I tried other solutions. Maybe is it because I'm not using MapControlTypes on my ControlSchema?
Solved!
You can do this, using a mapped type, but first you need to preserve the original type of the schema. If you add a type annotation to it, then information about specific fields and types will be lost. It will just be a BlockSchema
The easiest way to do this is to omit the annotation, and use an as const assertion to make the compiler infer literal types for type.
With this extra info in hand, we can then use a mapped type to transform the schema into an object type:
type InferType<T extends BlockSchema> = {
-readonly [P in keyof T['controls']]: MapControlTypes[T['controls'][P]['type']]
}
Playground Link
You can also use a function to create the schema, and be more selective about what gets the readonly treatment:
function buildBlockSchema<B extends BlockSchema>(b: B) {
return b
}
Playground Link

How do you write flexible typescript types/interfaces for client-side documents?

Let's say we have the following models:
const catSchema = new Schema({
name: String,
favoriteFood: { type: Schema.Types.ObjectId, ref: 'FoodType' },
});
const foodType = new Schema({
name: String,
});
Here we can see that favoriteFood on the catSchema is a reference to another collection in our database.
Now, let's say that the getAllCats api does not populate the favoriteFood field because it isn't necessary and therefor just returns the reference id for foodType. The response from the api might look like this:
[
{name: 'Fluffy', favoriteFood: '621001113833bd74d6f1fc8c'},
{name: 'Meowzer', favoriteFood: '621001113833bd74d6f1fc4b'}
]
However, the getOneCat api DOES populate the favoriteFood field with the corresponding document. It might look like this:
{
name: 'Fluffy',
favoriteFood: {
name: 'pizza'
}
}
My question, how does one write a client side interface/type for my cat document?
Do we do this?
interface IFavoriteFood {
name: string
}
interface ICat {
name: string,
favoriteFood: IFavoriteFood | string
}
Say we have a React functional component like this:
const Cat = (cat: ICat) => {
return (
<div>
${cat.favoriteFood.name}
</div>
)
}
We will get the following typescript error :
"Property 'name' does not exist on type 'string | IFavoriteFood'.
Property 'name' does not exist on type 'string'."
So, I have to do something like this to make typescript happy:
const Cat = (cat: ICat) => {
return (
<div>
${typeof cat.favoriteFood === 'string' ? 'favoriteFood is not populated': cat.favoriteFood.name}
</div>
)
}
Do I write two separate interfaces? One for the cat object with favoriteFood as a string for the objectId and one for cat with favoriteFood as the populated object?
interface IFavoriteFood {
name: string
}
interface ICat {
name: string,
favoriteFood: string
}
interface ICatWithFavoriteFood {
name: string,
favoriteFood: IFavoriteFood
}
const Cat = (cat: ICatWithFavoriteFood) => {
return (
<div>
${cat.favoriteFood.name}
</div>
)
}
Would love to hear how people approach this in their codebase. Also open to being pointed to any articles/resources that address this issue.
This:
favoriteFood: IFavoriteFood | string
Is a bad idea and will lead to a lot of ugly code trying to sort out when it's one data type versus the other.
I think a better approach (and one I personally use a lot) would be:
favoriteFoodId: string
favoriteFood?: IFavoriteFood
So favoriteFoodId is always there, and is always a string. And a full favoriteFood object is sometimes there.
Now to use that value is a very simple and standard null check.
const foodName = cat.favoriteFood?.name ?? '- ice cream, probably -'
Note, this does mean changing you schema a bit so that foreign keys are suffixed with Id to not clash with the keys that will contain the actual full association data.
You could extend this to the two interface approach as well, if you wanted to lock things down a bit tighter:
interface ICat {
name: string,
favoriteFoodId: string
favoriteFood?: null // must be omitted, undefined, or null
}
interface ICatWithFavoriteFood extends ICat {
favoriteFood: IFavoriteFood // required
}
But that's probably not necessary since handling nulls in your react component is usually cheap and easy.

Declare type with single required property and unbounded additional properties

I want to declare a function that takes an argument which has a type defined as an object with a single required property and any number of additional properties open format (T) while requiring the additional properties to adhere to the type signature of T. Specifically I'm trying to do something like this:
export myFunc<T>(props: {
data: {
key: string;
[x: T]: any;
}[]
}) { // myFunc code... }
The above definitely doesn't work. I've tried the approach using [x: string]: any; but that is too permissive and allows deviation from the type signature for T.
TypeScript doesn't currently have great support for the type you're talking about. The problem is that if an index signature exists, all named properties corresponding to the index signature must be compatible with it. Assuming that string is not assignable to T, the type {key: string, [k: string]: T} won't work. This restriction kind of makes sense, but it has been a source of frustration sometimes.
Maybe in the foreseeable future it will be possible to use arbitrary index signature types and negated types; if so, you could likely express the type as something like {key: string; [K: string & not "key"]: T}. That is, you will be able to explicitly exclude "key" from the index signature. But we're not there yet.
One thing you can do is use an intersection like {key: string} & {[k: string]: T} to circumvent the issue. But this doesn't work in some cases, especially yours, where you are likely to pass in an object literal:
declare function myFunc<T>(props: {
data: Array<{
key: string
} & { [x: string]: T }>
}): void;
myFunc<number>({ data: [{ key: "a" }] }); // error!
// ~~~~~~~~~~~~ <-- key is incompatible with index signature
The other workaround is to make the myFunc a generic function that infers the type of the props parameter as generic type P, and then uses a conditional type to verify that P meets your requirement. It's very long and messy and actually requires me to make it a curried function to allow you to manually specify T but then have the compiler infer P (the consequence of another currently missing feature in TypeScript):
type EnsureProps<
P extends { data: Array<{ key: string }> },
T,
A extends any[] = P['data']
> = {
data: {
[I in keyof A]: {
[K in keyof A[I]]?: K extends 'key' ? string : T
}
}
};
declare const myFuncMaker: <T>() =>
<P extends {
data: Array<{ key: string }>
}>(props: P & EnsureProps<P, T>) =>
void;
But at least it does work:
const myFunc = myFuncMaker<number>();
myFunc({ data: [{ key: "a", dog: 1, cat: "2" }] }); // error!
// ~~~ <-- string is not assignable to never
myFunc({ data: [{ key: "a", dog: 1 }, { key: "b", cat: 4 }] }); // okay
So let's step back. All of that is either unworkable, problematic, or a nightmare of type jugging. The compiler really doesn't want to let you represent this type. I'd probably suggest that you think about refactoring the myFunc() function so that the parameters are more amenable to TypeScript's type system. For example, if you push the additional properties of type T down one level but leave the "key" where it is, it would work well:
declare function myFunc<T>(props: {
data: Array<{
key: string,
more?: { [x: string]: T };
}>
}): void;
myFunc<number>({ data: [{ key: "a", more: { a: 1, b: 2 } }] });
I can see how that's a bit less convenient for you, but it might be worth it to save yourself the headache of having the compiler fighting against you.
Anyway, hope that helps; good luck!

Resources