Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use react-hook-form's setValue etc #80

Open
yyykkkyyy opened this issue Jul 21, 2022 · 2 comments
Open

How to use react-hook-form's setValue etc #80

yyykkkyyy opened this issue Jul 21, 2022 · 2 comments

Comments

@yyykkkyyy
Copy link

yyykkkyyy commented Jul 21, 2022

Hi ! I'm learning from this wonderful repository.

I would like to ask that can I use react-hook-form's setValue etc.

In this repo's Form structure, how to use setValue etc from other components.

I think that this repo's form structure offers really reusable error handling and labeling.
On the other hand, I don't find appropriate way to set values dynamically on this form structure.
I envision a form that automatically populates the next form with values based on the input values.

For instance, I tried the code like below:

<Form<UserValues typeof schema>
  ...
 >
    {({ formState, control, setValue, watch }) => (
       ....
    )}
</Form>

And below is my sample input field.

import { Controller, UseFormRegisterReturn } from 'react-hook-form';
import TextField from '@mui/material/TextField';

import { FieldWrapper, FieldWrapperPassThroughProps } from './FieldWrapper';

type InputFieldProps = FieldWrapperPassThroughProps & {
  type?: 'text' | 'email' | 'password';
  onChange?: any;
  defaultValue?: string;
  control: any;
  setValue?: any;
  name: string;
};

export const InputField = (props: InputFieldProps) => {
  const { type = 'text', label, onChange, defaultValue, control, setValue, name, error } = props;

  return (
      <FieldWrapper label={label} error={error}>
        <Controller
          name={name}
          control={control}
          defaultValue={defaultValue}
          render={({ field, fieldState, formState }) => {
            return (
              <TextField
                label={label}
                type={type}
                value={field.value}
                ref={field.ref}
                onChange={onChange}
              />
            );
          }}
        />
      </FieldWrapper>
    );
  };

I use MUI for input components, so I use a control variable and it is used for Controller in the component.
I can get setValue, watch and something, but I don't understand how to use these variables.

I understand this is very general and not critical question.

I would really appreciate it if you could answer my question.

Thanks.

@shobhitk8055
Copy link

shobhitk8055 commented Mar 4, 2024

@yyykkkyyy There is one solution to it using iife

export const LoginForm = ({ onSuccess }: LoginFormProps) => {
  const { login, isLoggingIn } = useAuth();

  var setNewValue;

  const handleClick = () => {
    setNewValue('email', 'abc');
  }

  return (
    <div>
      <Form<LoginValues, typeof schema>
        onSubmit={async (values) => {
          await login(values);
          onSuccess();
        }}
        schema={schema}
      >
        {({ register, formState, setValue }) => (
          <>
            {(() => {
              setNewValue = setValue;
            })()}
            <Button onClick={handleClick}>Change Value</Button>
            <InputField
              type="email"
              label="Email Address"
              error={formState.errors['email']}
              registration={register('email')}
            />
            <div>
              <Button isLoading={isLoggingIn} type="submit" className="w-full">
                Log in
              </Button>
            </div>
          </>
        )}
      </Form>
    </div>
  );
};

Similarly you can use other function which are available in useForm() hook

@shobhitk8055
Copy link

shobhitk8055 commented Mar 4, 2024

There is one more solution, but it is pretty big one. First you have to change the Form.tsx component to this

import clsx from 'clsx';
import * as React from 'react';
import { UseFormReturn, SubmitHandler, UseFormProps, FieldValues } from 'react-hook-form';

type FormProps<TFormValues extends FieldValues> = {
  className?: string;
  onSubmit: SubmitHandler<TFormValues>;
  children: React.ReactNode;
  options?: UseFormProps<TFormValues>;
  id?: string;
  methods: UseFormReturn<TFormValues>;
};

const Form = <
  TFormValues extends Record<string, unknown> = Record<string, unknown>>({
  onSubmit,
  children,
  className,
  id,
  methods
}: FormProps<TFormValues>) => {
  return (
    <form
      className={clsx('', className)}
      onSubmit={methods.handleSubmit(onSubmit)}
      id={id}
    >
      {children}
    </form>
  );
};

export default Form;

Then create a hook named useHookForm.ts

import { zodResolver } from "@hookform/resolvers/zod";
import { Path, PathValue, UseFormProps, useForm } from "react-hook-form";
import { ZodType, ZodTypeDef } from "zod";

export const useHookForm = <
  TFormValues extends Record<string, unknown> = Record<string, unknown>,
  Schema extends ZodType<unknown, ZodTypeDef, unknown> = ZodType<
    unknown,
    ZodTypeDef,
    unknown
  >
>(
  schema: Schema,
  options?: UseFormProps<TFormValues>,
) => {
  const methods = useForm<TFormValues>({
    ...options,
    resolver: schema && zodResolver(schema),
  });

  const setValues = (valuesToSet: Partial<TFormValues>) => {
    Object.keys(valuesToSet).forEach((fieldName) => {
      methods.setValue(
        fieldName as Path<TFormValues>,
        valuesToSet[fieldName] as PathValue<TFormValues, Path<TFormValues>>
      );
    });
  };
  return { methods, setValues };
};

Then in Login.tsx you can do like this

export const LoginForm = ({ onSuccess }: LoginFormProps) => {
  const { methods } = useHookForm<LoginValues, typeof schema>(schema);
  const { formState, control, setValue } = methods;

  const login = useLogin();
  const navigate = useNavigate();
  const { animate, callAfterAnimateFn } = useAnimateFn();

  const handleClick = () => {
    setValue('email', 'abc');
  }

  return (
    <AnimatePresence>
      {animate && (
        <motion.div {...animations}>
          <div className="card p-4 mt-4 mx-4">
            <Form<LoginValues>
              onSubmit={async (values) => {
                console.log(values);
                // values;
                // onSuccess();
                login.mutate(values, { onSuccess });
              }}
              methods={methods}
            >
              <InputField
                control={control}
                type={TextFieldTextType.TEXT}
                title="Email Address"
                error={formState.errors["email"]}
                name="email"
              />
              <InputField
                control={control}
                type={TextFieldTextType.PASSWORD}
                title="Password"
                error={formState.errors["password"]}
                name="password"
                wrapperClassName="mt-3"
              />
              <div className="d-flex justify-content-center">
                <Button
                  leftIcon={Locked}
                  isLoading={login.isLoading}
                  type="submit"
                  className="w-100 mt-3"
                >
                  Log In
                </Button>
              </div>
            </Form>
            <Link
              to="#"
              onClick={callAfterAnimateFn(() => navigate("/auth/forget"))}
              className="forget-link"
            >
              Forget Password
            </Link>
          </div>
          <p className="text-center mt-2">
            Don't have an account?{" "}
            <Link
              to="#"
              onClick={callAfterAnimateFn(() => navigate("/auth/register"))}
              className="forget-link"
            >
              Sign Up
            </Link>
          </p>
        </motion.div>
      )}
    </AnimatePresence>
  );
};

@alan2207 If you like this solution, I can change it in the whole app, and then I can raise a pr for it ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants