react-hook-form / resolvers

📋 Validation resolvers: Yup, Zod, Superstruct, Joi, Vest, Class Validator, io-ts, Nope, computed-types, typanion, Ajv, TypeBox, ArkType, Valibot, effect-ts and VineJS
https://react-hook-form.com/
MIT License
1.78k stars 161 forks source link

Type problems after update resolver to 1.3.0 #97

Closed william-hotmart closed 3 years ago

william-hotmart commented 3 years ago

After update @hookform/resolvers to the version 1.3.0, this code above no longer works.

The TS compiler points out the error:

Type 'Resolver<AssertsShape<Assign<Record<string, AnySchema<any, any, any> | Reference<unknown> | Lazy<any, any>>, { title: RequiredStringSchema<string, Record<string, any>>; buttonTitle: RequiredStringSchema<...>; exibitionMode: RequiredStringSchema<...>; link: RequiredStringSchema<...>; duration: NumberSchema<...>; }>>,...' is not assignable to type 'Resolver<FormData, object>'.
  Types of parameters 'values' and 'values' are incompatible.
    Type 'FormData' is not assignable to type '{ [x: string]: never; title: string; buttonTitle: string; exibitionMode: string; link: string; duration: number; }'.
      Index signature is missing in type 'FormData'.ts(2322)
form.d.ts(39, 5): The expected type comes from property 'resolver' which is declared here on type 'Partial<{ mode: "onBlur" | "onChange" | "onSubmit" | "onTouched"

If I comment the lines of exibitionMode and colorBackground the error disappears.

This is the code that I'm working with

import { useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import { object, string, number } from 'yup'

enum Color {
  DARK = 'dark',
  LIGHT = 'light',
  PURPLE = 'purple',
  ORANGE = 'orange',
  RED = 'red',
  GREEN = 'green'
}

enum ExibitionMode {
  INFINITY = 'infinity',
  LIMITED = 'limited'
}

interface FormData {
  title: string,
  buttonTitle: string,
  exibitionMode: ExibitionMode,
  link: string,
  duration: number,
  colorBackground: Color
}

const TITLE_MAX_LENGTH = 96
const BUTTON_TITLE_MAX_LENGTH = 28

const schema = object().shape({
  title: string().max(TITLE_MAX_LENGTH).required(),
  buttonTitle: string().max(BUTTON_TITLE_MAX_LENGTH).required(),
  exibitionMode: string().required(),
  link: string().required().url(),
  duration: number().when('exibitionMode', (exibitionMode, schema) => {
    return exibitionMode === ExibitionMode.LIMITED ? schema.min(1).required() : schema
  })
})

const Test = () => {
  const { register, handleSubmit } = useForm<FormData>({
    resolver: yupResolver(schema),
    mode: 'onBlur'
  })
}
jorisre commented 3 years ago

👋🏻 Hi Which version of Yup are you using ?

william-hotmart commented 3 years ago

I forgot. Sorry! "yup": 0.32.8" and "react-hook-form: 6.13.1"

jorisre commented 3 years ago

Thanks!

You get this error because your FormData contains more properties than your schema.

type MySchema = yup.InferType<typeof schema>;

You can see MySchema and FormData are different, that's why you get that error.

You've a second TS error because you're using TypeScript enum but yup.InferType could return an enum:

const schema = object({
  exibitionMode: string().oneOf(["infinity", "limited"]).required(),
});

type Schema = InferType<typeof schema>;
// Schema['exibitionMode'] = string instead of 'infinity' | 'limited'
william-hotmart commented 3 years ago
jorisre commented 3 years ago
  1. Yes, at this time, all values in FormData should be in the schema
  2. TS enum is a limitation of yup, InferType of yup couldn't return a TS enum ⬇️
const schema = object({
  exibitionMode: string().oneOf(["infinity", "limited"]).required(),
});

type Schema = InferType<typeof schema>;
// Schema['exibitionMode'] = string instead of 'infinity' | 'limited'
william-hotmart commented 3 years ago

I don't know why but with version 1.2.0 I'm not seeing those problems. Well, thanks @jorisre, you were very kind!

jorisre commented 3 years ago

@william-hotmart with version 1.2.0, types was wrong see above ⬇️

It returns multiples Record<string, any> Screen Shot 2020-12-31 at 19 59 39

With 1.3.0 Screen Shot 2020-12-31 at 20 00 14

EDIT: I've an idea to improve this :)

william-hotmart commented 3 years ago

Yeah, I see! So returning to this question: `If my form contain a value that I do not need to validate (like colorBackground), how to proceed?'

Do I just put it in the schema like colorBackground: string()?

jorisre commented 3 years ago

Yes you can as a workaround solution

william-hotmart commented 3 years ago

Ok! Thanks :smile:

vtrphan commented 3 years ago

i have some type errors too, and after fixing some of it i ended up having this error which i don't know how to solve: Typescript: Index signature is missing in type Problem occurred after updating to > 1.3.0

jorisre commented 3 years ago

@vtrphan could you provide a codesandbox please ?

vtrphan commented 3 years ago

i can't quite reproduce it in codeandsandbox, but i have included the schema definitions in a small snippet, and below is one of the errors i got: Type 'Resolver<AssertsShape<Assign<Record<string, AnySchema<any, any, any> | Reference<unknown> | Lazy<any, any>>, { salutation: RequiredStringSchema<string, Record<string, any>>; ... 20 more ...; concentrator: BooleanSchema<...>; }>>, object>' is not assignable to type 'Resolver<IFormInputs, object>'. Types of parameters 'values' and 'values' are incompatible. Type 'IFormInputs' is not assignable to type '{ [x: string]: never; salutation: string; firstName: string; lastName: string; companyName: string; address: { [x: string]: never; street: string; addressComplement: string; zip: string; city: string; country: string; }; ... 16 more ...; concentrator: boolean | undefined; }'. Types of property 'address' are incompatible. Type '{ street: string; addressComplement: string; zip: string; city: string; country: string; }' is not assignable to type '{ [x: string]: never; street: string; addressComplement: string; zip: string; city: string; country: string; }'. Property 'street' is incompatible with index signature. Type 'string' is not assignable to type 'never'. TS2322

Schema definition: https://codesandbox.io/s/testhookformresolvers-meelj

seifsg commented 3 years ago

@jorisre I'm facing same problem as @vtrphan I think this is an issue with type definition. This happened just right after the update. Here is a sample of my code:

const schema = yup.object().shape({
  email: yup
    .string()
    .email('Please enter a valid email address')
    .required('Please enter your email.'),
  password: yup
    .string()
    .required('Please enter your password.')
    .min(6, 'Password is too short!')
    .max(40, 'Password is too long!'),
  role: yup
    .string()
    .required('Please select a role.')
    .nullable(),
});
interface FormInterface {
  email: string;
  password: string;
  role: string;
}
const LoginForm = React.memo((props: LoginFormProps) => {

  const { control, handleSubmit, errors } = useForm<FormInterface>({
    resolver: yupResolver(schema),
  });

Error when hovering on resolver: Type 'Resolver<AssertsShape<Assign<Record<string, AnySchema<any, any, any> | Reference<unknown> | Lazy<any, any>>, { email: RequiredStringSchema<string | undefined, Record<string, any>>; password: RequiredStringSchema<...>; role: any; }>>, object>' is not assignable to type 'Resolver<FormInterface, object>'. Types of parameters 'values' and 'values' are incompatible. Type 'FormInterface' is not assignable to type '{ [x: string]: never; email: string; password: string; role: any; }'. Index signature is missing in type 'FormInterface'.ts(2322)

jorisre commented 3 years ago

@seifsg Thanks for reporting. I'll fix it tonight

vtrphan commented 3 years ago

@seifsg thanks for reporting

jorisre commented 3 years ago

Fixed in 1.3.2

Any feedback appreciated :)

Ulydev commented 3 years ago

What a coincidence, I just encountered this exact same issue on a new project, 30 minutes ago. Thank you for your quick response and fix, it works now! 💪

mrlubos commented 3 years ago

Hey! I had the same issue and can confirm it's now fixed. However, I am seeing this error message when running unit tests.

● Test suite failed to run

    Jest encountered an unexpected token

    This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.

    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/en/configuration.html

    Details:

    /home/runner/work/webapp/webapp/node_modules/@hookform/resolvers/dist/yup.js:2
    import { transformToNestObject } from 'react-hook-form';
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1350:14)
      at Object.<anonymous> (node_modules/@hookform/resolvers/yup.js:1:107)

Has something else changed since 1.3.0? Until then everything worked fine, this happens with 1.3.2.

william-hotmart commented 3 years ago

@mrlubos My solution to this problem was to the code below in my jest.config file

 "transformIgnorePatterns": [
    '/node_modules/(?!@hookform/*).+\\.[t|j]sx?$'
  ],

I don't know if is right, but it worked

jorisre commented 3 years ago

@mrlubos Yes since 1.3.0 something change in the module's output that's why you get this error. You can use the solution suggested by @william-hotmart or try the v2 beta, more info: https://github.com/react-hook-form/resolvers/issues/71#issuecomment-754623998

mrlubos commented 3 years ago

@william-hotmart @jorisre Thanks both! Will this new configuration be necessary in v2?

ljharb commented 3 years ago

@jorisre the implication is that you made a breaking change, albeit unintentional, in a minor version, and that you're not planning to fix it in the v1 line. Can you confirm?

(semver would dictate that you revert back to v1.2.x and publish it as a v1.3 latest, at the very least)

jorisre commented 3 years ago

Hi @ljharb 👋🏻

If we revert back to v1.2.x a new problem will occur for users which use webpacker. Possible work around: https://github.com/react-hook-form/resolvers/issues/71#issuecomment-754623998

The V2 fix all of these issues. We are running the V2 beta since months with success, I think we can apply the same fix on the version 1. I keep an eye on this issue and I'll fix V1 on the weekend

As a reminder:

jorisre commented 3 years ago

👋🏻 You can try the last version v1.3.6. The issue should be solved

rstcruzo commented 3 years ago

I am getting this error in 2.8.1 again. 2.8.0 is fine.

jorisre commented 3 years ago

I am getting this error in 2.8.1 again. 2.8.0 is fine.

Can you open a new issue with a reproductible example please?

andreisocaciu commented 3 years ago

Reproducible example here: https://codesandbox.io/s/https-github-com-react-hook-form-resolvers-issues-97-vpk5w?file=/src/App.tsx

castrogh commented 2 years ago

Hello!

I'm facing something like this.

Error Message: Type 'Control<FormData, object>' cannot be assigned to type 'Control<any, any>'.

`import React from 'react'; import { Button } from '../Button'; import { ControlledInput } from '../ControlledInput'; import { Container } from './styles'; import { useForm } from 'react-hook-form'; import * as yup from "yup"; import { yupResolver } from '@hookform/resolvers/yup';

type FormData = { name: string; email: string; password: string; password_confirm: string; }

const schema = yup.object({ name: yup.string().required("Informe o seu nome"), email: yup.string().email("E-mail inválido").required("Informe o e-mail"), password: yup.string().min(8, "A senha deve ter, no mínimo, 8 dígitos").required("Informe a senha"), password_confirm: yup.string().oneOf([yup.ref('password'), null], 'A senha de confirmação não é a mesma digitada no campo "Senha"').required("Digite a confirmação da senha") });

export function Form() { const { control, handleSubmit, formState:{ errors } } = useForm({ resolver: yupResolver(schema) });

function handleUserRegister(data: FormData) { console.log(data);

}

return (

' cannot be assigned to type 'Control'. icon="user" placeholder="Digite seu nome" placeholderTextColor = 'lightblue' error={errors.name} /> ' cannot be assigned to type 'Control'. icon="mail" placeholder="Digite seu e-mail" placeholderTextColor = 'lightblue' keyboardType="email-address" autoCapitalize='none' error={errors.email} /> ' cannot be assigned to type 'Control'. icon="lock" placeholder="Digite sua senha" placeholderTextColor = 'lightblue' secureTextEntry error={errors.password} /> ' cannot be assigned to type 'Control'. icon="lock" placeholder="Confirme sua senha" placeholderTextColor = 'lightblue' secureTextEntry error={errors.password_confirm} />
amanzrx4 commented 1 year ago

I'm also getting a similar error while working with material UI and RHF useController.


  const testSchema = yup.object({
    test: yup.string().required("Required"),
  });

  type TestInputs = yup.InferType<typeof testSchema>;

  const INITIAL_VALUES = {
    test: "",
  };

  const {
    register,
    handleSubmit,
    watch,
    setValue,
    reset,
    getValues,
    control,
    formState: { errors, isDirty, isSubmitting },
  } = useForm<TestInputs>({
    mode: "onSubmit",
    reValidateMode: "onChange",
    defaultValues: INITIAL_VALUES,
    resolver: yupResolver(testSchema),
    context: undefined,
    criteriaMode: "firstError",
    shouldFocusError: true,
    shouldUnregister: false,
    delayError: undefined,
  });

  return (
    <Box>
      <form>
        <ControlledTextField
          type="text"
          placeholder="Test"
          defaultValue={INITIAL_VALUES.test}
          control={control}  // shows an error
        />
      </form>
    </Box>
  );
};```

The Error : ```Type 'Control<AssertsShape<{ test: RequiredStringSchema<string | undefined, AnyObject>; }>, any>' is not assignable to type 'Control<FieldValues, any>'.
  Types of property '_reset' are incompatible.
    Type 'UseFormReset<AssertsShape<{ test: RequiredStringSchema<string | undefined, AnyObject>; }>>' is not assignable to type 'UseFormReset<FieldValues>'.
      Types of parameters 'values' and 'values' are incompatible.
        Type 'FieldValues | { [x: string]: any; } | ResetAction<FieldValues> | undefined' is not assignable to type 'AssertsShape<{ test: RequiredStringSchema<string | undefined, AnyObject>; }> | { test?: string | undefined; } | ResetAction<AssertsShape<{ test: RequiredStringSchema<...>; }>> | undefined'.
          Type 'ResetAction<FieldValues>' is not assignable to type 'AssertsShape<{ test: RequiredStringSchema<string | undefined, AnyObject>; }> | { test?: string | undefined; } | ResetAction<AssertsShape<{ test: RequiredStringSchema<...>; }>> | undefined'.
            Type 'ResetAction<FieldValues>' is not assignable to type 'ResetAction<AssertsShape<{ test: RequiredStringSchema<string | undefined, AnyObject>; }>>'.
              Type 'FieldValues' is not assignable to type 'AssertsShape<{ test: RequiredStringSchema<string | undefined, AnyObject>; }>'.ts(2322)
TextField.tsx(39, 3): The expected type comes from property 'control' which is declared here on type 'IntrinsicAttributes & ControlledTextFieldProps'```
AuthorProxy commented 1 year ago

same error

kleenkanteen commented 10 months ago

Work around is adding // @ts-expect-error ts(2322) before the problematic line so typescript ignores it. In your case, the before the line resolver: yupResolver(schema),.

I'm also getting a similar error while working with material UI and RHF useController.

Same. My error message:

Type 'Resolver<{ email?: string | undefined; password: string; }, any>' is not assignable to type 'Resolver<IFormInputs, any>'. Types of parameters 'options' and 'options' are incompatible. Type 'ResolverOptions<IFormInputs>' is not assignable to type 'ResolverOptions<{ email?: string | undefined; password: string; }>'. Type '{ email?: string | undefined; password: string; }' is not assignable to type 'IFormInputs'.ts(2322)

Code:

import type { FC } from 'react';
import { yupResolver } from '@hookform/resolvers/yup';
import { TextField } from '@mui/material';
import Head from 'next/head';
import { useForm, Controller } from 'react-hook-form';
import type { SubmitHandler } from 'react-hook-form';
import * as yup from 'yup';

interface IFormInputs {
  email: string;
  password: string;
}

const schema = yup.object().shape({
  email: yup.string().email(),
  password: yup.string().min(4).max(20).required(),
});

const Home: FC = () => {
  const {
    register,
    control, 
    handleSubmit,
    watch,
    formState: { errors },
  } = useForm<IFormInputs>({
    resolver: yupResolver(schema),
  });

  const onSubmit: SubmitHandler<IFormInputs> = (data) => console.log('data submitted: ', data);

  console.log(watch('email'));
  console.log('errors are', errors);

  return (
    <div>
      <Head>
        <title>ReceitaClient</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <h1>
          Welcome to <a href="https://nextjs.org">Next.js!</a>
        </h1>

        <p>
          Get started by editing <code>pages/index.js</code>
        </p>
        <form onSubmit={handleSubmit(onSubmit)}>
          <Controller
            name="email"
            control={control}
            defaultValue="example@dev.com"
            render={({ field }) => (
              <TextField
                {...field}
                label="Email"
                variant="outlined"
                error={!!errors.email}
                helperText={errors.email ? errors.email?.message : ''}
                fullWidth
                margin="dense"
              />
            )}
          />
          <br />
          <Controller
            name="password"
            control={control}
            defaultValue=""
            render={({ field }) => (
              <TextField
                {...field}
                type="password"
                label="Password"
                variant="outlined"
                error={!!errors.password}
                helperText={errors.password ? errors.password?.message : ''}
                fullWidth
                margin="dense"
              />
            )}
          />
          <input type="submit" />
        </form>
      </main>
    </div>
  );
};

export default Home;

My dependencies:

  "dependencies": {
    "@emotion/react": "^11.11.3",
    "@emotion/styled": "^11.11.0",
    "@hookform/resolvers": "^3.3.4",
    "@mui/material": "^5.15.2",
    "@mui/x-date-pickers": "^6.18.6",
    "@supabase/supabase-js": "^2.39.2",
    "@t3-oss/env-nextjs": "^0.7.1",
    "dayjs": "^1.11.10",
    "next": "^14.0.4",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-hook-form": "^7.49.2",
    "supabase": "^1.127.4",
    "yup": "^1.3.3",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@mui/types": "^7.2.12",
    "@next/eslint-plugin-next": "^14.0.3",
    "@types/eslint": "^8.44.7",
    "@types/node": "^18.17.0",
    "@types/react": "^18.2.37",
    "@types/react-dom": "^18.2.15",
    "@typescript-eslint/eslint-plugin": "^6.11.0",
    "@typescript-eslint/parser": "^6.11.0",
    "autoprefixer": "^10.4.14",
    "daisyui": "^4.4.24",
    "eslint": "^8.54.0",
    "postcss": "^8.4.31",
    "prettier": "^3.1.0",
    "prettier-plugin-tailwindcss": "^0.5.7",
    "tailwindcss": "^3.3.5",
    "typescript": "^5.3.3"
  },