3
votes

I'm trying to make a Formik wrapper which takes children as props and would render anything put inside. There are a couple forms to make which take different initial values and validation schema etc. The only thing in common thing is the grid layout. The goal is to have the access to Formik props like values, errors etc. in the child component and I have no idea how to pass it to its child. The form fields don't even show up.

The wrapper:

import React from 'react';
import { Formik, FormikConfig, FormikValues } from "formik";
import { Col, Layout, Row } from "antd";

const FormContainer: React.FC<FormikConfig<FormikValues>> = ({ children, ...props }) => {
    return <Formik
        {...props}
    >
        {props => (
            <Layout>
                <Row style={{ height: "100vh", display: "flex", alignItems: "center" }}>
                    <Col span={12}>
                        <Layout>
                            {/*this will be replaced with some background image*/}
                            <pre>{JSON.stringify(props.values, null, 2)}</pre>
                            <pre>{JSON.stringify(props.errors, null, 2)}</pre>
                        </Layout>
                    </Col>
                    <Col span={12}>
                        <Layout>
                            {/*here goes goes a Form from a different components*/}
                            {children}
                        </Layout>
                    </Col>
                </Row>
            </Layout>
        )}
    </Formik>
};

export default FormContainer;

I must be doing something wrong. I am unable to get any Formik props/values from anywhere else when I wrap FormContainer around anything.

My form example (so far):

import React from "react";
import { Field, Form } from "formik";
import { Col, Form as AntForm, Icon, Input, Row } from "antd";
import { initialValues, validationSchema } from "./fieldValidation";
import FormContainer from "../../../containers/FormContainer/FormContainer";

const RegisterPage: React.FC = () => {
    return (
        <FormContainer
            initialValues={initialValues}
            validationSchema={validationSchema}
            onSubmit={(data, { setSubmitting }) => {
                setSubmitting(true);

                setTimeout(() => {
                    alert(JSON.stringify(data, null, 2));
                    setSubmitting(false);
                }, 5000);
            }}
        >
            {({touched, errors}) => (
                <Form>
                    <Row gutter={[8, 8]}>
                        <Col span={12}>
                            <AntForm.Item
                                help={touched.firstName && errors.firstName ? errors.firstName : ""}
                                validateStatus={touched.firstName && errors.firstName ? "error" : undefined}
                            >
                                <Field
                                    name="firstName"
                                    prefix={<Icon type="solution" style={{ color: "rgba(0,0,0,.25)" }} />}
                                    placeholder="First name"
                                    as={Input}
                                />
                            </AntForm.Item>
                        </Col>
                        <Col span={12}>
                            <AntForm.Item
                                help={touched.lastName && errors.lastName ? errors.lastName : ""}
                                validateStatus={touched.lastName && errors.lastName ? "error" : undefined}
                            >
                                <Field
                                    name="lastName"
                                    prefix={<Icon type="solution" style={{ color: "rgba(0,0,0,.25)" }} />}
                                    placeholder="Last name"
                                    as={Input}
                                />
                            </AntForm.Item>
                        </Col>
                    </Row>
                </Form>
            )}
        </FormContainer>
    );
};

export default RegisterPage;

I'm stuck. What am I doing wrong here?

2
Just at first glance, you're using props twice, once when destructuring the arguments passed to your wrapper, and then again to name the arguments for the child render function. That seems a little weird to me; I think my IDE would be highlighting that as an error :)larz

2 Answers

9
votes

Here's how to pass the prop "propsToPass" from the parent to all his direct children:

const Parent = props => {
  const { children } = props;

  const childrenWithExtraProp = React.Children.map(children, child =>
    React.cloneElement(child, { propsToPass: "toChildren" })
  );

  return <div>{childrenWithExtraProp}</div>;
};

export default Parent;

So in this case, both children will have the prop "propsToPass"

<Parent>
  {/* this.props.propsToPass will be available in this component */}
  <Child></Child>
  {/* this.props.propsToPass will be available in this component */}
  <AnotherChild></AnotherChild>
</Parent>

You could do the same for your form.

1
votes

I don't see like rendering Formik as children is good idea here, especially that you are supposed to render one form in such FormWrapper. I would use render props here, so here is basic example how you can do it.

Anyway, I still can't get your concept of re-inventing FormWrapper if Formik provides its own wrapper:

https://jaredpalmer.com/formik/docs/api/formik

interface FormWrapperProps extends FormikConfig<FormikValues> {
    renderForm(props: FormWrapperProps): React.ReactNode
}

export const RegisterForm = (props: FormWrapperProps) => (
    <form>
        <input type="text"/>
        <input type="text"/>
    </form>
)

const FormWrapper: React.FC<FormWrapperProps> = (props) => {
    return (
        <div className="layout">
            {/*here goes goes a Form from a different components*/}
            {props.renderForm(props)}
        </div>
    )
}

const FormPage = () => {

    const props = {} as FormWrapperProps

    return (
        <FormWrapper
            {...props}
            renderForm={(props: FormWrapperProps) => <RegisterForm {...props} />}
        />
    )
}