0

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>;
}
2
  • That would require knowledge of typing at runtime. That's not possible. TypeScript code is compiled to JavaScript. You can use types at compile time. At run time, all you're left with is plain old JavaScript. Commented Jul 16, 2019 at 18:29
  • 1
    You could possibly encapsulate the two in yet another compound type and make the tuple optional. Commented Jul 16, 2019 at 18:30

1 Answer 1

4

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!

Link to code

Sign up to request clarification or add additional context in comments.

1 Comment

These are concepts that I know of, but I have not been able to abstract to arrive at these two conclusions. Thank you very much!!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.