Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React | How to Pass FormikProps one Component Up

I am trying to pass values and props, that formik needs 1 component up. I am using various little components for some forms, and I am passing them in a complex component that needs to pass them down to each individual render when called.

Basically all the FormikProps. Here is one such component.

import React, { Fragment } from 'react';
import debounce from 'debounce-promise';
import classNames from 'classnames';
import { Field, FormikProps, ErrorMessage } from 'formik';

import Asterisk from 'shared/common/components/element/Asterisk';
import { validateUsername } from '../../utils/index';

interface IValues {
  username?: string;
  email?: string;
}

export const InfoFields = (props: FormikProps<IValues>): JSX.Element => {
  const debounceUsernameValidation = (): void => {
    debounce(validateUsername, 500);
  };

  const { touched, errors } = props;

  return (
    <Fragment>
      <div className="pb-2">
        <label className="font-weight-bold" htmlFor="username">
          Username <Asterisk />
        </label>
        <Field
          validate={debounceUsernameValidation}
          className={classNames('form-control', {
            'is-invalid': errors.username && touched.username
          })}
          placeholder="Username (Required)"
          autoComplete="username"
          name="username"
          type="text"
        />
        <ErrorMessage name="username" component="div" className="text-danger" />
      </div>
      <div className="py-2">
        <label className="font-weight-bold">Email</label>
        <Field
          className={classNames('form-control', {
            'is-invalid': errors.email && touched.email
          })}
          autoComplete="email"
          placeholder="Email"
          name="email"
          type="email"
        />
        <ErrorMessage name="email" component="div" className="text-danger" />
      </div>
    </Fragment>
  );
};

export default InfoFields;

And here I am calling it inside the complex component:



  render(): ReactNode {
    const { mode } = this.props;
    return (
      <Formik
        initialValues={this.getInitialValues()}
        validationSchema={this.getValidationSchemas()}
        onSubmit={this.handleSubmit}
        validateOnBlur={false}
        render={({ status, isSubmitting }) =>
          (
            <Form>
              {status && (
                <div className="mb-3 text-danger" data-test="user-form-error-message">
                  {status}
                </div>
              )}
              {mode === ActionMode.ADD_USER && (
                <Fragment>
                  <InfoFields /> // This is the component from above
                </Fragment>
              )}
              <Button
                className="btn btn-primary w-100 mt-5"
                disabled={isSubmitting}
                loading={isSubmitting}
                type="submit"
              >
                {mode === ActionMode.ADD_USER && <span>CREATE USER</span>}
              </Button>
            </Form>
          ) as ReactNode
        }
      />
    );
  }

Now when I call that component inside another one, I get this error:

Uncaught TypeError: Cannot read property 'username' of undefined. It refers to this line of code:
errors.username && touched.username and this
errors.email && touched.email
interface IProps {
  doSubmit(service: object, values: object): LensesHttpResponse<string>;
  onSave(values: { username?: string; email?: string; password?: string; group?: string }): void;
  notify(config: object): void;
  mode: ActionMode;
  user: IUser;
}

interface IValues extends FormikValues {
  username?: string;
  email?: string;
  password?: string;
  group?: string;
}

I need a way to pass the props username, email and the rest to each individual compoenent.The problem is the component is called 2 renders down, so it doesn't have access to them

I am kind at a loss here. Can someone help me? Thanks!!


1 Answers

You aren't actually passing props to InfoFields although you've written that component to accept FormikProps<IValues>. Either you can pass Formik's props in like this:

<Formik
    render={formikProps => (
        <Form>
            // Other Code.

            <InfoFields {...formikProps} />

            // Other Code.
        </Form>
    )}
/>

Or (my personal preference), remove InfoField's props and use Field as a render prop, for example:

<Field
    name="username"
    validate={debounceUsernameValidation}
>
    {({ field, form }: FieldProps) => (
        <Fragment>
            <input
                {...field}
                className={classNames('form-control', {
                    'is-invalid': form.errors[field.name] &&
                        form.touched[field.name]
                })}
                placeholder="Username (Required)"
                type="text"
            />
            <ErrorMessage 
                name={field.name} 
                component="div" 
                className="text-danger" 
            />
        </Fragment>
    )}
</Field>

With the field render prop you can access the form values in components nested further down without passing props all over the place.

like image 75
Ciarán Tobin Avatar answered Oct 20 '25 16:10

Ciarán Tobin