Reputation: 172
Errors and Touched attributes are used together, and I was wondering if there is any way to tie the use of the two. To use as optional (?) seems to me very shallow.
Would there be a way to make it required to use 'touched' from the moment the 'errors' are filled?!
import { ChangeEvent, FocusEvent, Ref } from 'react';
export interface FormikProps<Value, Element> {
name: string;
value: Value;
onChange: (event: ChangeEvent<Element>) => void;
onBlur: (event: FocusEvent<Element>) => void;
error?: string | undefined;
touched?: boolean | undefined;
ref?: Ref<Element>;
}
Upvotes: 0
Views: 199
Reputation: 327754
Yes, if you can use a type alias instead of an interface, you'd use a union type to represent the constraint (along with an intersection to represent the common properties):
type FormikProps<Value, Element> = {
name: string;
value: Value;
onChange: (event: ChangeEvent<Element>) => void;
onBlur: (event: FocusEvent<Element>) => void;
ref?: Ref<Element>;
} & (
| { error: string; touched: boolean }
| { error?: never; touched?: never });
You can see if you use it that you have to use both or neither:
const useBoth: FormikProps<string, number> = {
name: "F",
value: "v",
onChange: e => console.log(e),
onBlur: e => console.log(e),
error: "hey",
touched: false
}; // okay
const useNeither: FormikProps<string, number> = {
name: "F",
value: "v",
onChange: e => console.log(e),
onBlur: e => console.log(e)
}; // okay
or else you get errors:
const justUseError: FormikProps<string, number> = { // error!
// ~~~~~~~~~~~~ <-- "touched is missing"
name: "F",
value: "v",
onChange: e => console.log(e),
onBlur: e => console.log(e),
error: "hey",
};
const justUseTouched: FormikProps<string, number> = { // error!
// ~~~~~~~~~~~~~~ <-- "error is missing"
name: "F",
value: "v",
onChange: e => console.log(e),
onBlur: e => console.log(e),
touched: true
};
You might find it a little annoying to use this type inside a function that checks it, since it seems that there's a bit of an issue with control flow analysis here:
function f(fp: FormikProps<string, number>) {
if (typeof fp.error === "string") {
fp.touched.valueOf(); // error!
//~~~~~~~~~~ <-- object is possibly undefined ?!
fp.touched!.valueOf(); // okay, asserted
}
if (fp.error) {
fp.touched.valueOf(); // this works for some reason
}
}
but it's still probably more useful than your original definition.
I agree with a commenter that you might want to encapsulate the two together:
interface Encapsulated<Value, Element> {
name: string;
value: Value;
onChange: (event: ChangeEvent<Element>) => void;
onBlur: (event: FocusEvent<Element>) => void;
ref?: Ref<Element>;
errTouched?: { error: string; touched: boolean };
}
That type is straightforward and the type system will understand it more:
function f2(fp: Encapsulated<string, number>) {
if (fp.errTouched) {
fp.errTouched.error.valueOf(); // okay
fp.errTouched.touched.valueOf(); // okay
}
}
Anyway, hope that helps; good luck!
Upvotes: 4