Brian
Brian

Reputation: 356

How to useFormik pass Props?

using Reactjs with typescript

I want to pass the useFormik hook to the component props.

The reason for this is to reduce unnecessary lines and increase reuse.

My current code

...
const formik = useFormik({
    initialValues: { userName: ''},
    validationSchema,
    onSubmit: (values) => {}
})

return (
  <Form>
    {/* A place to make a component. */}
    <Text
       id="userName"
       fullWidth
       label="Name"
       defaultValue={formik.values.userName}
       onChange={formik.handleChange}
       onBlur={formik.handleBlur}
       error={formik.touched.userName && Boolean(formik.errors.userName)}
       helperText={formik.touched.userName && formik.errors.userName}
    >
    {/* A place to make a component. */}
  </Form>
)

Custom component, which is the main point of the question.


interface props {
    id: string;
    formik : what, // How do I deliver the prop here?
  }

const TextFieldCustom = ({ id, formik }: props) => {
    return (
     <Text
         id={id}
         fullWidth
         label={id}
         defaultValue={formik.values.userName}
         onChange={formik.handleChange}
         onBlur={formik.handleBlur}
         error={formik.touched.userName && Boolean(formik.errors.userName)}
         helperText={formik.touched.userName && formik.errors.userName}
    >

  );
};

My code completed because of your answer.

...
const formik = useFormik({
    initialValues: { userName: ''},
    validationSchema,
    onSubmit: (values) => {}
})

return (
  <Form>
    {/* A place to make a component. */}
    <TextFieldCustom id="username" formik={formik}/>
    {/* A place to make a component. */}
  </Form>
)

I want your good solution.

Upvotes: 1

Views: 7196

Answers (4)

Victor Eke
Victor Eke

Reputation: 545

If you're in a Next.js SSR environment, formik will initially return undefined before initialization so you might need to add a guard to help silent this error.

type FormProps = {
  formik: FormikProps<YourInterface>;
}

function Form({formik}: FormProps) {
  if(!formik) return null // Guard to prevent errors

  // You can use formik however you want from here.
  console.log(formik)

  return (
    <form onSubmit={formik.handleSubmit}>
      <label>
        <input
          name="firstName"
          value={formik.values.firstname}
          onChange={formik.handleChange}
        />
      </label>
    </form>
  )
}

Another way you can achieve this is by wrapping the component with the useFormik <Form> component and then get the form context using useFormikContext hook. Here's how that would work.

import { Form, Formik } from "formik";

function App() {
  return (
   <Formik initialValues={initialValues} onSubmit={(values) => console.log(values)}>
     <Form>
      <CustomComponent />
     </Form>
   </div>
 )
}

Now the form values should be accessible via the useFormik context.

// CustomComponent.tsx
import { useFormikContext } from "formik";

function CustomComponent() {
  const formikContext = useFormikContext<YourInterface>();
  // Next.js guard to prevent initialization errors
  if(!formikContext) return;

  const { values, handleChange } = formikContext;

  return (
    <div>
      <label>
        <input
          name="firstName"
          value={values.firstName}
          onChange={handleChange}
        />
      </label>
    </form>
  )
}

Upvotes: 1

Caio NNC
Caio NNC

Reputation: 11

To achieve what you want, you can take the Manjunath answer as base and make these changes.

The parent component would be like this:

const { initial, validators } = schemaSignUpForm//import your yup validator;
...
const formik = useFormik<YourInterface>({
    initialValues: initial, // you can destructure your interface object here. Not obliged to the schema above
    validationSchema,
    onSubmit: (values) => {}
})

And the new component which receives the formik props would be like this:

interface props {
  id: string;
  formik : FormikProps<YourInterface>
}

const TextFieldCustom = ({ id, formik }: props) => {
    return (
     <Text
         id={id}
         fullWidth
         label={id}
         defaultValue={formik.values.userName}
         onChange={formik.handleChange}
         onBlur={formik.handleBlur}
         error={formik.touched.userName && 
Boolean(formik.errors.userName)}
         helperText={formik.touched.userName && formik.errors.userName}
    >

  );
};

Upvotes: 1

Manjunath V M
Manjunath V M

Reputation: 51

You can use FormikProps

import { FormikProps } from 'formik';

interface props {
    id: string;
    formik : FormikProps<{username: string}>
  }

const TextFieldCustom = ({ id, formik }: props) => {
    return (
     <Text
         id={id}
         fullWidth
         label={id}
         defaultValue={formik.values.userName}
         onChange={formik.handleChange}
         onBlur={formik.handleBlur}
         error={formik.touched.userName && Boolean(formik.errors.userName)}
         helperText={formik.touched.userName && formik.errors.userName}
    >

  );
};

Upvotes: 1

crls_b
crls_b

Reputation: 720

If you want to access formik helpers from the child component you can use useFormikContext. I think it's easier.

From your code I would also recommend to use Formik component as parent of Form component as it needs it but that could be another thing.

Here's what I would do (note that I've substituted Form component in favor of form html tag):

Parent component:

export interface IFormData {
  username: string;
}

const validationSchema = Yup.object().shape({
  userName: Yup.string()
    .required("Username required"),
});

const ParentComponent = () => {
  const formikConfig = useFormik({
    initialValues: { userName: "" },
    validationSchema,
    onSubmit: (values) => {},
  });

  return (
    <form onSubmit={formikConfig.handleSubmit}>
      <TextCompo id="id" />
    </form>
  );
};

export default ParentComponent;

Children component:

interface ITextCompoProps {
  id: string;
}

const TextFieldCustom = (props: ITextCompoProps) => {
  const { id } = props;
  const context = useFormikContext<IFormData>();

  return (
    <Text
      id={id}
      fullWidth
      label={id}
      defaultValue={context.values.userName}
      onChange={context.handleChange}
      onBlur={context.handleBlur}
      error={!!(context.errors.username && context.touched.username)}
      helperText={context.touched.username && context.errors.username}
    />
  );
};

export default TextFieldCustom;

Upvotes: 6

Related Questions