Typescript type with get function - reactjs

I'm looking for a way to define a getter for a type object in React.
type User= {
firstName?: string
lastName: string
age: number
}
In my React components I want to display firstName primarily and lastName if firstName is undefined. This logic also covers assignment e.g. author= u.firstName ?? u.lastName.
How can I define a getter than makes sure the logic is used consistently throughout the application? Like:
author = u.getName()
in code and:
{{u.getName()}
in templates.
From my research it seems like I should replace the type with class to be able to declare member functions. Do you know if type support member functions?

You can easily define a type that has a function/method:
type User = {
firstName: string;
lastName: string;
age: number;
getFullName(): string;
};
But it's just is what it name implies: a type. It doesn't do anything. The tricky part is setting the value:
const u: User = {
firstName: "John",
lastName: "Doe",
age: 30,
getFullName() {
return this.firstName + " " + this.lastName;
}
}
If you don't want to define the getFullName method every time, you should probably use a class.

Related

How to instantiate an object complying with zod schema defined

In my React project, I have integrated the zod library, like so:
const schema = z.object({
id: z.string().optional(),
name: z.string().min(1, "Name is required"),
country: z.string().min(1, "Country is required")
});
type Customer = z.infer<typeof schema>;
I am trying to instantiate an object of the type Customer.
In one of my components, I try to use the type in this manner:
I can't figure out what the right syntax would be for this:
const [customer, setCustomer] = useState(new Customer());
The error I get on that new Customer() syntax is: 'Customer' only refers to a type, but is being used as a value here.ts(2693)
I figured I could maybe declare a class of the Customer type, but that didn't work either.
Got any ideas, please?

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.

Why Can't I Use Template Literals to Create Strings as Keys In a New Object? 😕

I have some Array of Objects, with a sample Object as such:
{
id: 10,
name: "Clementina XYZ",
username: "Moriah.Stanton",
email: "foo.bar#quux.biz",
address: {
street: "Kattie Turnpike",
suite: "Suite 555",
city: "Lebsackbury",
zipcode: "31428-2261",
geo: {
lat: "-38.2386",
lng: "57.2232"
}
},
phone: "555-648-3804",
website: "example.com",
company: {
name: "Acme LLC",
catchPhrase: "Centralized empowering task-force",
bs: "target end-to-end models"
}
}
I also have a function that is working just fine to find users that work in the same company.name and map over them to create Objects with name, address and phone.
function buildCompanyDirectory(company, directory) {
return directory
.filter(user => user.company.name === company)
.map(({ name, address, phone }) => ({
name,
address,
phone
}));
}
Now, a new task:
// TODO: Build an Array of company directories
So, I expect to have a final Array consisting of 🔑s that are derived from user.company.name with values that are Arrays of Objects with 🔑s of name, address and phone. So, something like:
[{
"Hoeger LLC": [{ }]
}]
As part of solving this, I feel (evidently JS doesn't agree!) that I should be able to use template literals to create new objects using 🔑s that are derived from the values from another object.
That nonworking code looks like this:
const directories = users.map((user) => {
return {
`${user.company.name}` : buildCompanyDirectory(company.name, users) // ERROR: Property assignment expected
};
});
It doesn't seem that I should need to resort to Object.keys() or anything like that. This feels more like some type of 'syntax' error or just bad notation.
Naturally, I would like to know the simplest way to get this working out! Thanks.
The problem is that key construction in object literals is special-cased in JS. You can use either a literal string or a bare identifier.
To allow an expression as a key in an object literal, JS uses the following hack:
const a = 'aaaa';
const data = {[`this_is_${a}`]: 10}
data.this_is_aaaa === 10; // true.
The expression within [] will be evaluated and used as a key.

how to represent state object as typescript interface

I thought I am comfortable with Javascript and React, but currently suffering through typescript learning curve. I have my react state defined as:
state = {
fields: { // list of fields
symbol: '',
qty: '',
side: ''
},
fieldErrors: {}
};
I want to be able to use it as following (dictionary):
onInputChange = (name :string, value :string, error :string) => {
const fields = this.state.fields;
const fieldErrors = this.state.fieldErrors;
fields[name] = value;
fieldErrors[name] = error;
this.setState({fields, fieldErrors});
}
How do I represent my state in terms of Typescript? I am trying something like:
interface IFields {
name: string
}
interface IOrderEntryState {
fields: IFields,
fieldErrors: IFields
}
Pardon if my question sounds illiterate, totally new at this. Thanks
Based on your snippet, it looks like fields[name] is assigning an arbitrary key to that object. So you probably want to use an index signature to represent that instead of the hardcoded key name as you have now.
So your interfaces probably should look more like this:
interface IFields {
// This is an index signature. It means this object can hold
// a key of any name, and they can be accessed and set using
// bracket notation: `this.state.fields["somekey"]`
[name: string]: string
}
interface IOrderEntryState {
fields: IFields,
fieldErrors: IFields
}
If you want a dictionary you can declare one using generic type with index property. It would look like this:
interface Dictionary<TKey, TVal> {
[key: TKey]: TVal;
}
interface IOrderEntryState {
fields: Dictionary<string, string>,
fieldErrors: Dictionary<string, string>
}
This makes IOrderEntryState.fields have arbitrary string attribute names with string values.

flow nested object values not accesible

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.

Resources