Refactoring almost identical components, formik react












0















I have two identical components, and only few differences (one). There are two many repetitive code and boilerplate, but I am unsure how to refactor this so that I only need to supply a config probably.



LoginPage.js



import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';

import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import { LoginValidationSchema } from 'validations/AuthValidationSchema';

function LoginPage({ username, onChangeUsername, onSubmitForm }) {
return (
<div>
<Helmet>
<title>Login</title>
</Helmet>
<Formik
initialValues={{ username, password: '' }}
validationSchema={LoginValidationSchema}
onSubmit={onSubmitForm}
render={({ isSubmitting, isValid, handleChange }) => (
<Form>
<FastField
type="text"
name="username"
render={({ field }) => (
<input
{...field}
onChange={e => {
handleChange(e);
onChangeUsername(e);
}}
/>
)}
/>
<ErrorMessage name="username" component="div" aria-live="polite" />
<FastField type="password" name="password" />
<ErrorMessage name="password" component="div" aria-live="polite" />
<button type="submit" disabled={isSubmitting || !isValid}>
Login
</button>
<FormDebug />
</Form>
)}
/>
<Link to="/auth/forgot_password">Forgot Password</Link>
</div>
);
}

LoginPage.propTypes = {
username: PropTypes.string,
onSubmitForm: PropTypes.func.isRequired,
onChangeUsername: PropTypes.func.isRequired,
};

export default LoginPage;


ForgotPasswordPage.js



import React from 'react';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';

import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import { ForgotPasswordValidationSchema } from 'validations/AuthValidationSchema';

function ForgotPasswordPage({ username, onChangeUsername, onSubmitForm }) {
return (
<div>
<Helmet>
<title>Forgot Password</title>
</Helmet>
<Formik
initialValues={{ username }}
validationSchema={ForgotPasswordValidationSchema}
onSubmit={onSubmitForm}
render={({ isSubmitting, isValid, handleChange }) => (
<Form>
<FastField
type="text"
name="username"
render={({ field }) => (
<input
{...field}
onChange={e => {
handleChange(e);
onChangeUsername(e);
}}
/>
)}
/>
<ErrorMessage name="username" component="div" aria-live="polite" />
<FormDebug />
<button type="submit" disabled={isSubmitting || !isValid}>
Reset Password
</button>
</Form>
)}
/>
</div>
);
}

ForgotPasswordPage.propTypes = {
username: PropTypes.string,
onSubmitForm: PropTypes.func.isRequired,
onChangeUsername: PropTypes.func.isRequired,
};

export default ForgotPasswordPage;


How to you refactor this, if you were me.



I am thinking HOC., but I am unsure how to call pass the "children" which is different










share|improve this question





























    0















    I have two identical components, and only few differences (one). There are two many repetitive code and boilerplate, but I am unsure how to refactor this so that I only need to supply a config probably.



    LoginPage.js



    import React from 'react';
    import { Link } from 'react-router-dom';
    import { Helmet } from 'react-helmet';
    import { Formik, FastField, Form, ErrorMessage } from 'formik';

    import PropTypes from 'prop-types';
    import { FormDebug } from 'utils/FormDebug';
    import { LoginValidationSchema } from 'validations/AuthValidationSchema';

    function LoginPage({ username, onChangeUsername, onSubmitForm }) {
    return (
    <div>
    <Helmet>
    <title>Login</title>
    </Helmet>
    <Formik
    initialValues={{ username, password: '' }}
    validationSchema={LoginValidationSchema}
    onSubmit={onSubmitForm}
    render={({ isSubmitting, isValid, handleChange }) => (
    <Form>
    <FastField
    type="text"
    name="username"
    render={({ field }) => (
    <input
    {...field}
    onChange={e => {
    handleChange(e);
    onChangeUsername(e);
    }}
    />
    )}
    />
    <ErrorMessage name="username" component="div" aria-live="polite" />
    <FastField type="password" name="password" />
    <ErrorMessage name="password" component="div" aria-live="polite" />
    <button type="submit" disabled={isSubmitting || !isValid}>
    Login
    </button>
    <FormDebug />
    </Form>
    )}
    />
    <Link to="/auth/forgot_password">Forgot Password</Link>
    </div>
    );
    }

    LoginPage.propTypes = {
    username: PropTypes.string,
    onSubmitForm: PropTypes.func.isRequired,
    onChangeUsername: PropTypes.func.isRequired,
    };

    export default LoginPage;


    ForgotPasswordPage.js



    import React from 'react';
    import { Helmet } from 'react-helmet';
    import { Formik, FastField, Form, ErrorMessage } from 'formik';

    import PropTypes from 'prop-types';
    import { FormDebug } from 'utils/FormDebug';
    import { ForgotPasswordValidationSchema } from 'validations/AuthValidationSchema';

    function ForgotPasswordPage({ username, onChangeUsername, onSubmitForm }) {
    return (
    <div>
    <Helmet>
    <title>Forgot Password</title>
    </Helmet>
    <Formik
    initialValues={{ username }}
    validationSchema={ForgotPasswordValidationSchema}
    onSubmit={onSubmitForm}
    render={({ isSubmitting, isValid, handleChange }) => (
    <Form>
    <FastField
    type="text"
    name="username"
    render={({ field }) => (
    <input
    {...field}
    onChange={e => {
    handleChange(e);
    onChangeUsername(e);
    }}
    />
    )}
    />
    <ErrorMessage name="username" component="div" aria-live="polite" />
    <FormDebug />
    <button type="submit" disabled={isSubmitting || !isValid}>
    Reset Password
    </button>
    </Form>
    )}
    />
    </div>
    );
    }

    ForgotPasswordPage.propTypes = {
    username: PropTypes.string,
    onSubmitForm: PropTypes.func.isRequired,
    onChangeUsername: PropTypes.func.isRequired,
    };

    export default ForgotPasswordPage;


    How to you refactor this, if you were me.



    I am thinking HOC., but I am unsure how to call pass the "children" which is different










    share|improve this question



























      0












      0








      0


      1






      I have two identical components, and only few differences (one). There are two many repetitive code and boilerplate, but I am unsure how to refactor this so that I only need to supply a config probably.



      LoginPage.js



      import React from 'react';
      import { Link } from 'react-router-dom';
      import { Helmet } from 'react-helmet';
      import { Formik, FastField, Form, ErrorMessage } from 'formik';

      import PropTypes from 'prop-types';
      import { FormDebug } from 'utils/FormDebug';
      import { LoginValidationSchema } from 'validations/AuthValidationSchema';

      function LoginPage({ username, onChangeUsername, onSubmitForm }) {
      return (
      <div>
      <Helmet>
      <title>Login</title>
      </Helmet>
      <Formik
      initialValues={{ username, password: '' }}
      validationSchema={LoginValidationSchema}
      onSubmit={onSubmitForm}
      render={({ isSubmitting, isValid, handleChange }) => (
      <Form>
      <FastField
      type="text"
      name="username"
      render={({ field }) => (
      <input
      {...field}
      onChange={e => {
      handleChange(e);
      onChangeUsername(e);
      }}
      />
      )}
      />
      <ErrorMessage name="username" component="div" aria-live="polite" />
      <FastField type="password" name="password" />
      <ErrorMessage name="password" component="div" aria-live="polite" />
      <button type="submit" disabled={isSubmitting || !isValid}>
      Login
      </button>
      <FormDebug />
      </Form>
      )}
      />
      <Link to="/auth/forgot_password">Forgot Password</Link>
      </div>
      );
      }

      LoginPage.propTypes = {
      username: PropTypes.string,
      onSubmitForm: PropTypes.func.isRequired,
      onChangeUsername: PropTypes.func.isRequired,
      };

      export default LoginPage;


      ForgotPasswordPage.js



      import React from 'react';
      import { Helmet } from 'react-helmet';
      import { Formik, FastField, Form, ErrorMessage } from 'formik';

      import PropTypes from 'prop-types';
      import { FormDebug } from 'utils/FormDebug';
      import { ForgotPasswordValidationSchema } from 'validations/AuthValidationSchema';

      function ForgotPasswordPage({ username, onChangeUsername, onSubmitForm }) {
      return (
      <div>
      <Helmet>
      <title>Forgot Password</title>
      </Helmet>
      <Formik
      initialValues={{ username }}
      validationSchema={ForgotPasswordValidationSchema}
      onSubmit={onSubmitForm}
      render={({ isSubmitting, isValid, handleChange }) => (
      <Form>
      <FastField
      type="text"
      name="username"
      render={({ field }) => (
      <input
      {...field}
      onChange={e => {
      handleChange(e);
      onChangeUsername(e);
      }}
      />
      )}
      />
      <ErrorMessage name="username" component="div" aria-live="polite" />
      <FormDebug />
      <button type="submit" disabled={isSubmitting || !isValid}>
      Reset Password
      </button>
      </Form>
      )}
      />
      </div>
      );
      }

      ForgotPasswordPage.propTypes = {
      username: PropTypes.string,
      onSubmitForm: PropTypes.func.isRequired,
      onChangeUsername: PropTypes.func.isRequired,
      };

      export default ForgotPasswordPage;


      How to you refactor this, if you were me.



      I am thinking HOC., but I am unsure how to call pass the "children" which is different










      share|improve this question
















      I have two identical components, and only few differences (one). There are two many repetitive code and boilerplate, but I am unsure how to refactor this so that I only need to supply a config probably.



      LoginPage.js



      import React from 'react';
      import { Link } from 'react-router-dom';
      import { Helmet } from 'react-helmet';
      import { Formik, FastField, Form, ErrorMessage } from 'formik';

      import PropTypes from 'prop-types';
      import { FormDebug } from 'utils/FormDebug';
      import { LoginValidationSchema } from 'validations/AuthValidationSchema';

      function LoginPage({ username, onChangeUsername, onSubmitForm }) {
      return (
      <div>
      <Helmet>
      <title>Login</title>
      </Helmet>
      <Formik
      initialValues={{ username, password: '' }}
      validationSchema={LoginValidationSchema}
      onSubmit={onSubmitForm}
      render={({ isSubmitting, isValid, handleChange }) => (
      <Form>
      <FastField
      type="text"
      name="username"
      render={({ field }) => (
      <input
      {...field}
      onChange={e => {
      handleChange(e);
      onChangeUsername(e);
      }}
      />
      )}
      />
      <ErrorMessage name="username" component="div" aria-live="polite" />
      <FastField type="password" name="password" />
      <ErrorMessage name="password" component="div" aria-live="polite" />
      <button type="submit" disabled={isSubmitting || !isValid}>
      Login
      </button>
      <FormDebug />
      </Form>
      )}
      />
      <Link to="/auth/forgot_password">Forgot Password</Link>
      </div>
      );
      }

      LoginPage.propTypes = {
      username: PropTypes.string,
      onSubmitForm: PropTypes.func.isRequired,
      onChangeUsername: PropTypes.func.isRequired,
      };

      export default LoginPage;


      ForgotPasswordPage.js



      import React from 'react';
      import { Helmet } from 'react-helmet';
      import { Formik, FastField, Form, ErrorMessage } from 'formik';

      import PropTypes from 'prop-types';
      import { FormDebug } from 'utils/FormDebug';
      import { ForgotPasswordValidationSchema } from 'validations/AuthValidationSchema';

      function ForgotPasswordPage({ username, onChangeUsername, onSubmitForm }) {
      return (
      <div>
      <Helmet>
      <title>Forgot Password</title>
      </Helmet>
      <Formik
      initialValues={{ username }}
      validationSchema={ForgotPasswordValidationSchema}
      onSubmit={onSubmitForm}
      render={({ isSubmitting, isValid, handleChange }) => (
      <Form>
      <FastField
      type="text"
      name="username"
      render={({ field }) => (
      <input
      {...field}
      onChange={e => {
      handleChange(e);
      onChangeUsername(e);
      }}
      />
      )}
      />
      <ErrorMessage name="username" component="div" aria-live="polite" />
      <FormDebug />
      <button type="submit" disabled={isSubmitting || !isValid}>
      Reset Password
      </button>
      </Form>
      )}
      />
      </div>
      );
      }

      ForgotPasswordPage.propTypes = {
      username: PropTypes.string,
      onSubmitForm: PropTypes.func.isRequired,
      onChangeUsername: PropTypes.func.isRequired,
      };

      export default ForgotPasswordPage;


      How to you refactor this, if you were me.



      I am thinking HOC., but I am unsure how to call pass the "children" which is different







      reactjs refactoring higher-order-components formik






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Jan 1 at 8:25









      Praveen Rao Chavan.G

      1,534719




      1,534719










      asked Jan 1 at 7:14









      Joey HipolitoJoey Hipolito

      1,15552869




      1,15552869
























          3 Answers
          3






          active

          oldest

          votes


















          2





          +100









          Apologies if you're not looking for a general answer, but I don't think you'll improve maintainability by generalising what may just be seemingly correlated components. I expect these components will drift further apart as you mature your application, e.g. by adding social login, option to 'remember me', captchas, option for retrieving both username and password by email, different handling of an unknown username when retrieving password vs signing in, etc. Also, this is a part of your component you really don't want to get wrong, so KISS and all. Finally, consider if there really is a third use case for such a semi-generalised login-or-retrieve-password form component.



          Still, minor improvement could be made by e.g. creating a reusable UsernameField component, the usage of which will be simple and consistent to both cases. Also consider a withValidation HOC adding an error message to a field. If you really want to stretch it, you could have a withSubmit HOC for Formik, passing all props to Formik, rendering children (which you would pass handleChange prop) and a submit button. I assume form itself uses context to pass state to ErrorMessage and FastField.






          share|improve this answer

































            1














            I may be missing some complexity not stated here, but this looks as simple as creating a generic Functional Component that accepts a couple more passed-in properties. I did a diff and you would only need to add 'title', 'buttonText', and, if you like, 'type' to your props, for sure. You could also send initialValues object as a prop, instead of deriving it from 'type'.



            I mean, did you try the following?



            import React from 'react';
            import { Link } from 'react-router-dom';
            import { Helmet } from 'react-helmet';
            import { Formik, FastField, Form, ErrorMessage } from 'formik';

            import PropTypes from 'prop-types';
            import { FormDebug } from 'utils/FormDebug';
            import * as schema from 'validations/AuthValidationSchema';

            function AuthPage({ buttonText, initialValues, title, type, username,
            onChangeUsername, onSubmitForm }) {
            const authSchema = type === 'login'
            ? schema.LoginValidationSchema
            : schema.ForgotPasswordValidationSchema;

            return (
            <div>
            <Helmet>
            <title>{title}</title>
            </Helmet>
            <Formik
            initialValues={initialValues}
            validationSchema={authSchema}
            onSubmit={onSubmitForm}
            render={({ isSubmitting, isValid, handleChange }) => (
            <Form>
            <FastField
            type="text"
            name="username"
            render={({ field }) => (
            <input
            {...field}
            onChange={e => {
            handleChange(e);
            onChangeUsername(e);
            }}
            />
            )}
            />
            <ErrorMessage name="username" component="div" aria-live="polite" />
            {type === 'forgot' &&
            <FastField type="password" name="password" />
            <ErrorMessage name="password" component="div" aria-live="polite" />
            }
            <button type="submit" disabled={isSubmitting || !isValid}>
            {buttonText}
            </button>
            <FormDebug />
            </Form>
            )}
            />
            <Link to="/auth/forgot_password">Forgot Password</Link>
            </div>
            );
            }

            AuthPage.propTypes = {
            buttonText: PropTypes.string,
            initialValues: PropTypes.object,
            title: PropTypes.string,
            type: PropTypes.oneOf(['login', 'forgot'])
            username: PropTypes.string,
            onSubmitForm: PropTypes.func.isRequired,
            onChangeUsername: PropTypes.func.isRequired,
            };

            export default AuthPage;


            (Only thing I can't remember is whether the conditional render of password field and its ErrorMessage needs to be wrapped in a div or not to make them one element)



            If you don't want to pass in initial values:



              const initialVals = type === 'login'
            ? { username, password: ''}
            : { username }
            ...
            initialValues={initialVals}


            and remove it from propTypes



            Only other thing I'm not sure of is why FormDebug is placed differently in the two versions. I've left it after the button.






            share|improve this answer

































              0














              Those two pages have separate concerns, so I wouldn't suggest pulling logic that can be used for both cases as that tends to add complexity by moving code further apart even if it does remove a few duplicate lines. In this case a good strategy may be to think of things that you reuse within components.



              For example you can wrap the formik component in your own wrapper and then wrap for example an input component that works with your new form. A good exercise in reducing boiler plate could be to aim for some variant of this end result



              <CoolForm 
              initialValues={{ username, password: '' }}
              validationSchema={LoginValidationSchema}
              >
              <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
              <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
              <CoolFormSubmit>
              Login
              </CoolFormSubmit>
              <FormDebug />
              </CoolForm>


              That's just an idea, but the nice thing about this strategy is that if you want to refactor formik out for whatever reason, its really simple because all your code now is wrapped in CoolForm components, so you only end up changing the implementation of your own components, although that benefit in this case is very small.






              share|improve this answer

























                Your Answer






                StackExchange.ifUsing("editor", function () {
                StackExchange.using("externalEditor", function () {
                StackExchange.using("snippets", function () {
                StackExchange.snippets.init();
                });
                });
                }, "code-snippets");

                StackExchange.ready(function() {
                var channelOptions = {
                tags: "".split(" "),
                id: "1"
                };
                initTagRenderer("".split(" "), "".split(" "), channelOptions);

                StackExchange.using("externalEditor", function() {
                // Have to fire editor after snippets, if snippets enabled
                if (StackExchange.settings.snippets.snippetsEnabled) {
                StackExchange.using("snippets", function() {
                createEditor();
                });
                }
                else {
                createEditor();
                }
                });

                function createEditor() {
                StackExchange.prepareEditor({
                heartbeatType: 'answer',
                autoActivateHeartbeat: false,
                convertImagesToLinks: true,
                noModals: true,
                showLowRepImageUploadWarning: true,
                reputationToPostImages: 10,
                bindNavPrevention: true,
                postfix: "",
                imageUploader: {
                brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
                contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
                allowUrls: true
                },
                onDemand: true,
                discardSelector: ".discard-answer"
                ,immediatelyShowMarkdownHelp:true
                });


                }
                });














                draft saved

                draft discarded


















                StackExchange.ready(
                function () {
                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53993704%2frefactoring-almost-identical-components-formik-react%23new-answer', 'question_page');
                }
                );

                Post as a guest















                Required, but never shown

























                3 Answers
                3






                active

                oldest

                votes








                3 Answers
                3






                active

                oldest

                votes









                active

                oldest

                votes






                active

                oldest

                votes









                2





                +100









                Apologies if you're not looking for a general answer, but I don't think you'll improve maintainability by generalising what may just be seemingly correlated components. I expect these components will drift further apart as you mature your application, e.g. by adding social login, option to 'remember me', captchas, option for retrieving both username and password by email, different handling of an unknown username when retrieving password vs signing in, etc. Also, this is a part of your component you really don't want to get wrong, so KISS and all. Finally, consider if there really is a third use case for such a semi-generalised login-or-retrieve-password form component.



                Still, minor improvement could be made by e.g. creating a reusable UsernameField component, the usage of which will be simple and consistent to both cases. Also consider a withValidation HOC adding an error message to a field. If you really want to stretch it, you could have a withSubmit HOC for Formik, passing all props to Formik, rendering children (which you would pass handleChange prop) and a submit button. I assume form itself uses context to pass state to ErrorMessage and FastField.






                share|improve this answer






























                  2





                  +100









                  Apologies if you're not looking for a general answer, but I don't think you'll improve maintainability by generalising what may just be seemingly correlated components. I expect these components will drift further apart as you mature your application, e.g. by adding social login, option to 'remember me', captchas, option for retrieving both username and password by email, different handling of an unknown username when retrieving password vs signing in, etc. Also, this is a part of your component you really don't want to get wrong, so KISS and all. Finally, consider if there really is a third use case for such a semi-generalised login-or-retrieve-password form component.



                  Still, minor improvement could be made by e.g. creating a reusable UsernameField component, the usage of which will be simple and consistent to both cases. Also consider a withValidation HOC adding an error message to a field. If you really want to stretch it, you could have a withSubmit HOC for Formik, passing all props to Formik, rendering children (which you would pass handleChange prop) and a submit button. I assume form itself uses context to pass state to ErrorMessage and FastField.






                  share|improve this answer




























                    2





                    +100







                    2





                    +100



                    2




                    +100





                    Apologies if you're not looking for a general answer, but I don't think you'll improve maintainability by generalising what may just be seemingly correlated components. I expect these components will drift further apart as you mature your application, e.g. by adding social login, option to 'remember me', captchas, option for retrieving both username and password by email, different handling of an unknown username when retrieving password vs signing in, etc. Also, this is a part of your component you really don't want to get wrong, so KISS and all. Finally, consider if there really is a third use case for such a semi-generalised login-or-retrieve-password form component.



                    Still, minor improvement could be made by e.g. creating a reusable UsernameField component, the usage of which will be simple and consistent to both cases. Also consider a withValidation HOC adding an error message to a field. If you really want to stretch it, you could have a withSubmit HOC for Formik, passing all props to Formik, rendering children (which you would pass handleChange prop) and a submit button. I assume form itself uses context to pass state to ErrorMessage and FastField.






                    share|improve this answer















                    Apologies if you're not looking for a general answer, but I don't think you'll improve maintainability by generalising what may just be seemingly correlated components. I expect these components will drift further apart as you mature your application, e.g. by adding social login, option to 'remember me', captchas, option for retrieving both username and password by email, different handling of an unknown username when retrieving password vs signing in, etc. Also, this is a part of your component you really don't want to get wrong, so KISS and all. Finally, consider if there really is a third use case for such a semi-generalised login-or-retrieve-password form component.



                    Still, minor improvement could be made by e.g. creating a reusable UsernameField component, the usage of which will be simple and consistent to both cases. Also consider a withValidation HOC adding an error message to a field. If you really want to stretch it, you could have a withSubmit HOC for Formik, passing all props to Formik, rendering children (which you would pass handleChange prop) and a submit button. I assume form itself uses context to pass state to ErrorMessage and FastField.







                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited Jan 7 at 21:47

























                    answered Jan 5 at 22:56









                    corollacorolla

                    3,56811615




                    3,56811615

























                        1














                        I may be missing some complexity not stated here, but this looks as simple as creating a generic Functional Component that accepts a couple more passed-in properties. I did a diff and you would only need to add 'title', 'buttonText', and, if you like, 'type' to your props, for sure. You could also send initialValues object as a prop, instead of deriving it from 'type'.



                        I mean, did you try the following?



                        import React from 'react';
                        import { Link } from 'react-router-dom';
                        import { Helmet } from 'react-helmet';
                        import { Formik, FastField, Form, ErrorMessage } from 'formik';

                        import PropTypes from 'prop-types';
                        import { FormDebug } from 'utils/FormDebug';
                        import * as schema from 'validations/AuthValidationSchema';

                        function AuthPage({ buttonText, initialValues, title, type, username,
                        onChangeUsername, onSubmitForm }) {
                        const authSchema = type === 'login'
                        ? schema.LoginValidationSchema
                        : schema.ForgotPasswordValidationSchema;

                        return (
                        <div>
                        <Helmet>
                        <title>{title}</title>
                        </Helmet>
                        <Formik
                        initialValues={initialValues}
                        validationSchema={authSchema}
                        onSubmit={onSubmitForm}
                        render={({ isSubmitting, isValid, handleChange }) => (
                        <Form>
                        <FastField
                        type="text"
                        name="username"
                        render={({ field }) => (
                        <input
                        {...field}
                        onChange={e => {
                        handleChange(e);
                        onChangeUsername(e);
                        }}
                        />
                        )}
                        />
                        <ErrorMessage name="username" component="div" aria-live="polite" />
                        {type === 'forgot' &&
                        <FastField type="password" name="password" />
                        <ErrorMessage name="password" component="div" aria-live="polite" />
                        }
                        <button type="submit" disabled={isSubmitting || !isValid}>
                        {buttonText}
                        </button>
                        <FormDebug />
                        </Form>
                        )}
                        />
                        <Link to="/auth/forgot_password">Forgot Password</Link>
                        </div>
                        );
                        }

                        AuthPage.propTypes = {
                        buttonText: PropTypes.string,
                        initialValues: PropTypes.object,
                        title: PropTypes.string,
                        type: PropTypes.oneOf(['login', 'forgot'])
                        username: PropTypes.string,
                        onSubmitForm: PropTypes.func.isRequired,
                        onChangeUsername: PropTypes.func.isRequired,
                        };

                        export default AuthPage;


                        (Only thing I can't remember is whether the conditional render of password field and its ErrorMessage needs to be wrapped in a div or not to make them one element)



                        If you don't want to pass in initial values:



                          const initialVals = type === 'login'
                        ? { username, password: ''}
                        : { username }
                        ...
                        initialValues={initialVals}


                        and remove it from propTypes



                        Only other thing I'm not sure of is why FormDebug is placed differently in the two versions. I've left it after the button.






                        share|improve this answer






























                          1














                          I may be missing some complexity not stated here, but this looks as simple as creating a generic Functional Component that accepts a couple more passed-in properties. I did a diff and you would only need to add 'title', 'buttonText', and, if you like, 'type' to your props, for sure. You could also send initialValues object as a prop, instead of deriving it from 'type'.



                          I mean, did you try the following?



                          import React from 'react';
                          import { Link } from 'react-router-dom';
                          import { Helmet } from 'react-helmet';
                          import { Formik, FastField, Form, ErrorMessage } from 'formik';

                          import PropTypes from 'prop-types';
                          import { FormDebug } from 'utils/FormDebug';
                          import * as schema from 'validations/AuthValidationSchema';

                          function AuthPage({ buttonText, initialValues, title, type, username,
                          onChangeUsername, onSubmitForm }) {
                          const authSchema = type === 'login'
                          ? schema.LoginValidationSchema
                          : schema.ForgotPasswordValidationSchema;

                          return (
                          <div>
                          <Helmet>
                          <title>{title}</title>
                          </Helmet>
                          <Formik
                          initialValues={initialValues}
                          validationSchema={authSchema}
                          onSubmit={onSubmitForm}
                          render={({ isSubmitting, isValid, handleChange }) => (
                          <Form>
                          <FastField
                          type="text"
                          name="username"
                          render={({ field }) => (
                          <input
                          {...field}
                          onChange={e => {
                          handleChange(e);
                          onChangeUsername(e);
                          }}
                          />
                          )}
                          />
                          <ErrorMessage name="username" component="div" aria-live="polite" />
                          {type === 'forgot' &&
                          <FastField type="password" name="password" />
                          <ErrorMessage name="password" component="div" aria-live="polite" />
                          }
                          <button type="submit" disabled={isSubmitting || !isValid}>
                          {buttonText}
                          </button>
                          <FormDebug />
                          </Form>
                          )}
                          />
                          <Link to="/auth/forgot_password">Forgot Password</Link>
                          </div>
                          );
                          }

                          AuthPage.propTypes = {
                          buttonText: PropTypes.string,
                          initialValues: PropTypes.object,
                          title: PropTypes.string,
                          type: PropTypes.oneOf(['login', 'forgot'])
                          username: PropTypes.string,
                          onSubmitForm: PropTypes.func.isRequired,
                          onChangeUsername: PropTypes.func.isRequired,
                          };

                          export default AuthPage;


                          (Only thing I can't remember is whether the conditional render of password field and its ErrorMessage needs to be wrapped in a div or not to make them one element)



                          If you don't want to pass in initial values:



                            const initialVals = type === 'login'
                          ? { username, password: ''}
                          : { username }
                          ...
                          initialValues={initialVals}


                          and remove it from propTypes



                          Only other thing I'm not sure of is why FormDebug is placed differently in the two versions. I've left it after the button.






                          share|improve this answer




























                            1












                            1








                            1







                            I may be missing some complexity not stated here, but this looks as simple as creating a generic Functional Component that accepts a couple more passed-in properties. I did a diff and you would only need to add 'title', 'buttonText', and, if you like, 'type' to your props, for sure. You could also send initialValues object as a prop, instead of deriving it from 'type'.



                            I mean, did you try the following?



                            import React from 'react';
                            import { Link } from 'react-router-dom';
                            import { Helmet } from 'react-helmet';
                            import { Formik, FastField, Form, ErrorMessage } from 'formik';

                            import PropTypes from 'prop-types';
                            import { FormDebug } from 'utils/FormDebug';
                            import * as schema from 'validations/AuthValidationSchema';

                            function AuthPage({ buttonText, initialValues, title, type, username,
                            onChangeUsername, onSubmitForm }) {
                            const authSchema = type === 'login'
                            ? schema.LoginValidationSchema
                            : schema.ForgotPasswordValidationSchema;

                            return (
                            <div>
                            <Helmet>
                            <title>{title}</title>
                            </Helmet>
                            <Formik
                            initialValues={initialValues}
                            validationSchema={authSchema}
                            onSubmit={onSubmitForm}
                            render={({ isSubmitting, isValid, handleChange }) => (
                            <Form>
                            <FastField
                            type="text"
                            name="username"
                            render={({ field }) => (
                            <input
                            {...field}
                            onChange={e => {
                            handleChange(e);
                            onChangeUsername(e);
                            }}
                            />
                            )}
                            />
                            <ErrorMessage name="username" component="div" aria-live="polite" />
                            {type === 'forgot' &&
                            <FastField type="password" name="password" />
                            <ErrorMessage name="password" component="div" aria-live="polite" />
                            }
                            <button type="submit" disabled={isSubmitting || !isValid}>
                            {buttonText}
                            </button>
                            <FormDebug />
                            </Form>
                            )}
                            />
                            <Link to="/auth/forgot_password">Forgot Password</Link>
                            </div>
                            );
                            }

                            AuthPage.propTypes = {
                            buttonText: PropTypes.string,
                            initialValues: PropTypes.object,
                            title: PropTypes.string,
                            type: PropTypes.oneOf(['login', 'forgot'])
                            username: PropTypes.string,
                            onSubmitForm: PropTypes.func.isRequired,
                            onChangeUsername: PropTypes.func.isRequired,
                            };

                            export default AuthPage;


                            (Only thing I can't remember is whether the conditional render of password field and its ErrorMessage needs to be wrapped in a div or not to make them one element)



                            If you don't want to pass in initial values:



                              const initialVals = type === 'login'
                            ? { username, password: ''}
                            : { username }
                            ...
                            initialValues={initialVals}


                            and remove it from propTypes



                            Only other thing I'm not sure of is why FormDebug is placed differently in the two versions. I've left it after the button.






                            share|improve this answer















                            I may be missing some complexity not stated here, but this looks as simple as creating a generic Functional Component that accepts a couple more passed-in properties. I did a diff and you would only need to add 'title', 'buttonText', and, if you like, 'type' to your props, for sure. You could also send initialValues object as a prop, instead of deriving it from 'type'.



                            I mean, did you try the following?



                            import React from 'react';
                            import { Link } from 'react-router-dom';
                            import { Helmet } from 'react-helmet';
                            import { Formik, FastField, Form, ErrorMessage } from 'formik';

                            import PropTypes from 'prop-types';
                            import { FormDebug } from 'utils/FormDebug';
                            import * as schema from 'validations/AuthValidationSchema';

                            function AuthPage({ buttonText, initialValues, title, type, username,
                            onChangeUsername, onSubmitForm }) {
                            const authSchema = type === 'login'
                            ? schema.LoginValidationSchema
                            : schema.ForgotPasswordValidationSchema;

                            return (
                            <div>
                            <Helmet>
                            <title>{title}</title>
                            </Helmet>
                            <Formik
                            initialValues={initialValues}
                            validationSchema={authSchema}
                            onSubmit={onSubmitForm}
                            render={({ isSubmitting, isValid, handleChange }) => (
                            <Form>
                            <FastField
                            type="text"
                            name="username"
                            render={({ field }) => (
                            <input
                            {...field}
                            onChange={e => {
                            handleChange(e);
                            onChangeUsername(e);
                            }}
                            />
                            )}
                            />
                            <ErrorMessage name="username" component="div" aria-live="polite" />
                            {type === 'forgot' &&
                            <FastField type="password" name="password" />
                            <ErrorMessage name="password" component="div" aria-live="polite" />
                            }
                            <button type="submit" disabled={isSubmitting || !isValid}>
                            {buttonText}
                            </button>
                            <FormDebug />
                            </Form>
                            )}
                            />
                            <Link to="/auth/forgot_password">Forgot Password</Link>
                            </div>
                            );
                            }

                            AuthPage.propTypes = {
                            buttonText: PropTypes.string,
                            initialValues: PropTypes.object,
                            title: PropTypes.string,
                            type: PropTypes.oneOf(['login', 'forgot'])
                            username: PropTypes.string,
                            onSubmitForm: PropTypes.func.isRequired,
                            onChangeUsername: PropTypes.func.isRequired,
                            };

                            export default AuthPage;


                            (Only thing I can't remember is whether the conditional render of password field and its ErrorMessage needs to be wrapped in a div or not to make them one element)



                            If you don't want to pass in initial values:



                              const initialVals = type === 'login'
                            ? { username, password: ''}
                            : { username }
                            ...
                            initialValues={initialVals}


                            and remove it from propTypes



                            Only other thing I'm not sure of is why FormDebug is placed differently in the two versions. I've left it after the button.







                            share|improve this answer














                            share|improve this answer



                            share|improve this answer








                            edited Jan 4 at 14:51

























                            answered Jan 3 at 21:00









                            eoneon

                            87411118




                            87411118























                                0














                                Those two pages have separate concerns, so I wouldn't suggest pulling logic that can be used for both cases as that tends to add complexity by moving code further apart even if it does remove a few duplicate lines. In this case a good strategy may be to think of things that you reuse within components.



                                For example you can wrap the formik component in your own wrapper and then wrap for example an input component that works with your new form. A good exercise in reducing boiler plate could be to aim for some variant of this end result



                                <CoolForm 
                                initialValues={{ username, password: '' }}
                                validationSchema={LoginValidationSchema}
                                >
                                <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
                                <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
                                <CoolFormSubmit>
                                Login
                                </CoolFormSubmit>
                                <FormDebug />
                                </CoolForm>


                                That's just an idea, but the nice thing about this strategy is that if you want to refactor formik out for whatever reason, its really simple because all your code now is wrapped in CoolForm components, so you only end up changing the implementation of your own components, although that benefit in this case is very small.






                                share|improve this answer






























                                  0














                                  Those two pages have separate concerns, so I wouldn't suggest pulling logic that can be used for both cases as that tends to add complexity by moving code further apart even if it does remove a few duplicate lines. In this case a good strategy may be to think of things that you reuse within components.



                                  For example you can wrap the formik component in your own wrapper and then wrap for example an input component that works with your new form. A good exercise in reducing boiler plate could be to aim for some variant of this end result



                                  <CoolForm 
                                  initialValues={{ username, password: '' }}
                                  validationSchema={LoginValidationSchema}
                                  >
                                  <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
                                  <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
                                  <CoolFormSubmit>
                                  Login
                                  </CoolFormSubmit>
                                  <FormDebug />
                                  </CoolForm>


                                  That's just an idea, but the nice thing about this strategy is that if you want to refactor formik out for whatever reason, its really simple because all your code now is wrapped in CoolForm components, so you only end up changing the implementation of your own components, although that benefit in this case is very small.






                                  share|improve this answer




























                                    0












                                    0








                                    0







                                    Those two pages have separate concerns, so I wouldn't suggest pulling logic that can be used for both cases as that tends to add complexity by moving code further apart even if it does remove a few duplicate lines. In this case a good strategy may be to think of things that you reuse within components.



                                    For example you can wrap the formik component in your own wrapper and then wrap for example an input component that works with your new form. A good exercise in reducing boiler plate could be to aim for some variant of this end result



                                    <CoolForm 
                                    initialValues={{ username, password: '' }}
                                    validationSchema={LoginValidationSchema}
                                    >
                                    <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
                                    <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
                                    <CoolFormSubmit>
                                    Login
                                    </CoolFormSubmit>
                                    <FormDebug />
                                    </CoolForm>


                                    That's just an idea, but the nice thing about this strategy is that if you want to refactor formik out for whatever reason, its really simple because all your code now is wrapped in CoolForm components, so you only end up changing the implementation of your own components, although that benefit in this case is very small.






                                    share|improve this answer















                                    Those two pages have separate concerns, so I wouldn't suggest pulling logic that can be used for both cases as that tends to add complexity by moving code further apart even if it does remove a few duplicate lines. In this case a good strategy may be to think of things that you reuse within components.



                                    For example you can wrap the formik component in your own wrapper and then wrap for example an input component that works with your new form. A good exercise in reducing boiler plate could be to aim for some variant of this end result



                                    <CoolForm 
                                    initialValues={{ username, password: '' }}
                                    validationSchema={LoginValidationSchema}
                                    >
                                    <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
                                    <CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
                                    <CoolFormSubmit>
                                    Login
                                    </CoolFormSubmit>
                                    <FormDebug />
                                    </CoolForm>


                                    That's just an idea, but the nice thing about this strategy is that if you want to refactor formik out for whatever reason, its really simple because all your code now is wrapped in CoolForm components, so you only end up changing the implementation of your own components, although that benefit in this case is very small.







                                    share|improve this answer














                                    share|improve this answer



                                    share|improve this answer








                                    edited Jan 9 at 11:07

























                                    answered Jan 9 at 11:02









                                    Daniel EscobedoDaniel Escobedo

                                    516




                                    516






























                                        draft saved

                                        draft discarded




















































                                        Thanks for contributing an answer to Stack Overflow!


                                        • Please be sure to answer the question. Provide details and share your research!

                                        But avoid



                                        • Asking for help, clarification, or responding to other answers.

                                        • Making statements based on opinion; back them up with references or personal experience.


                                        To learn more, see our tips on writing great answers.




                                        draft saved


                                        draft discarded














                                        StackExchange.ready(
                                        function () {
                                        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53993704%2frefactoring-almost-identical-components-formik-react%23new-answer', 'question_page');
                                        }
                                        );

                                        Post as a guest















                                        Required, but never shown





















































                                        Required, but never shown














                                        Required, but never shown












                                        Required, but never shown







                                        Required, but never shown

































                                        Required, but never shown














                                        Required, but never shown












                                        Required, but never shown







                                        Required, but never shown







                                        Popular posts from this blog

                                        MongoDB - Not Authorized To Execute Command

                                        How to fix TextFormField cause rebuild widget in Flutter

                                        in spring boot 2.1 many test slices are not allowed anymore due to multiple @BootstrapWith