How to instantiate an object complying with zod schema defined - reactjs

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?

Related

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.

Typescript type with get function

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.

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.

Object or String type conditional with yup validation

I am using yup in combination with Formik in my React application to validate a TypeScript model/interface.
We previously had a yup.object for the follwing schema:
export const InputSchema = yup.object({
id: yup.string(),
name: yup.string().required().min(MIN_DESC_LENGTH).max(_MAX_NAME_LENGTH),
description: yup.string().required().matches(/(?!^\d+$)^[\s\S]+$/, 'Please enter a valid description').min(MIN_DESC_LENGTH).max(_MAX_DESC_LENGTH),
contact: yup.string().required()
});
As we changed our interface now, name and description fields can either be a string or an object.
I still want to validate my form with yup, so I tried to use name: yup.mixed() and description: yup.mixed() but now I get obviously problems with the min.() and .matches() functions.
Is it possible to have a string OR object condition? So if it is a yup.string() then, min/max will be used, otherwise just yup.object().
I looked for a way to do this in a simple way but couldn't find any, but the solution they give is to use yup.lazy.
For your case it would be
Yup.object({
...
name: Yup.lazy(value => {
switch (typeof value) {
case 'object':
return Yup.object(); // schema for object
case 'string':
return Yup.string().min(MIN_DESC_LENGTH).max(_MAX_NAME_LENGTH); // schema for string
default:
return Yup.mixed(); // here you can decide what is the default
}
}),
...
})
Another way you can do is like this gist.
It creates a custom method with .addMethod that receives schemas and validates all. Pretty good approach

How to use interface to type function argument in Flow

I'm trying to implement a React component that contains a list of options and shows their id and name. I want this component to be reusable so I define an interface Option to ensure the required fields are always provided.
And here comes the issue: if I pass any type with more fields than those 2 { id, name, /* anything */}, Flow complains. Is it not possible to use interfaces in Flow like this?
Here's the minimal relevant code:
interface Option {
id: string,
name: string
}
const List = (options: Option[]) => {
options.forEach(o => null)
}
type ImplementsOption = {
id: string,
name: string,
description: string
}
const plans: ImplementsOption[] = []
List(plans)
Error:
Cannot call List with plans bound to options because property description is missing in Option 1 but exists in ImplementsOption [2] in array element.
Trying with casting:
List((plans: Option[]))
And also with classes:
class ComplexOption implements Option {
id: string
name: string
}
const complexOptions: ComplexOption[] = []
List(complexOptions)
Nothing seems to work!
There is a playground with all these snippets already.
Imagine we had a list of ImplementsOption: [{ id: 'id', name: 'name', description: 'description' }, ...]. Now we pass it into the List function, which has the signature Option[] => void. This is totally valid from the point of view of List since ImplementOption is a supertype of Option. However, there is no guarantee in the List function that it won't modify the list that is passed in. Thus, the function could add an element of type Option to the list, which would be valid for a Option[] but invalid for a ImplementsOption[].
To fix this, you can type plans as a $ReadOnlyArray<Option>, which tells Flow that the List function will not modify the elements (try flow).
Reference issues #4425, #4483, or #5206 for more information.

Resources