Clear sub-form values on conditional hide of nested fields

Viewed 310

Using React+Formik, I want to create a reusable component that we can use to conditionally show/hide nested subforms (of any complexity).

Every time it becomes hidden, we wish to clear the values so that those values don't get submitted.

Below, a simple hide/show component called OptionalFeature is shown.

const OptionalFeature = ({
  toggle,
  children
}) => {
  if (toggle) {
    return <div>{children}</div>
  } else {
    return null;
  }
}

It can be tested by pasting into https://codesandbox.io/s/zkrk5yldz But as you can see in the demo, making the children invisible does not clear their values. Ideally each child can define it's own clearValue behavior (the example is very simple, we want to have more complex nested forms).

What's the clean solution to clear the fullname field by extending OptionalFeature class in a generic, reusable way?

I already tried creating a cleanup function and calling it from OptionalFeature inside the if-block, but it does not seem very idiomatic.

// Helper styles for demo
import "./helper.css";
import { DisplayFormikState } from "./helper";

import React from "react";
import { render } from "react-dom";
import { Formik } from "formik";

// Generic reusable component to show/hide sub-forms
const OptionalFeature = ({
  toggle,
  children
}) => {
  if (toggle) {
    return <div>{children}</div>
  } else {
    return null;
  }
}

const App = () => (
  <div className="app">
    <Formik
      initialValues={{ email: "", anonymous: false, fullname:"" }}
      onSubmit={async values => {
        await new Promise(resolve => setTimeout(resolve, 500));
        alert(JSON.stringify(values, null, 2));
      }}
    >
      {props => {
        const {
          values,
          touched,
          errors,
          isSubmitting,
          handleChange,
          handleSubmit
        } = props;
        return (
          <form onSubmit={handleSubmit}>
            <input
              id="email"
              placeholder="Enter your email"
              type="text"
              value={values.email}
              onChange={handleChange}
            />

            {/* This checkbox should show/hide next field */}
            <div style={{ display: "white-space:nowrap" }}>
              <label htmlFor="anonymous" style={{ display: "inline-block"}}>Anonymous</label>
              <input 
                id="anonymous" 
                type="checkbox" 
                name="anonymous" 
                value={values.anonymous}
                onChange={handleChange}
                style={{ display: "inline-block", width: "20%"}}
                />
            </div>


            <OptionalFeature
              toggle={!values.anonymous}
            >

              {/* Imagine this subform comes from a different file */}
              <input
                id="fullname"
                placeholder="Enter your full name"
                type="text"
                value={values.fullname}
                onChange={handleChange}
              />

            </OptionalFeature>


            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>

            <DisplayFormikState {...props} />
          </form>
        );
      }}
    </Formik>
  </div>
);

render(<App />, document.getElementById("root"));

1 Answers

Here is my existing approach, waiting for a better answer:

const OptionalFeature = ({
  toggle,
  onHide,
  children
}) => {
  if (toggle) {
    return <div>{children}</div>
  } else {
    // useEffect needed because onHide function could trigger anything.
    // Also to avoid calling multiple times.
    useEffect(() => {
      onHide();
    }, [toggle, onHide])
    return null;
  }
}

Then later invoke a cleanup function onHide:

    <Formik
      initialValues={{ email: "", anonymous: false, fullname:"" }}
    ...>
       {props => {
        const {
          values,
          isSubmitting,
          handleChange,
          handleSubmit
        } = props;

        // to clean up all prop values inside the OptionalFeature
        const clearFullName = () => 
        {
          values.fullname = ""
        }
        return (
         //...
    
         <OptionalFeature
              toggle={!values.anonymous}
              onHide={clearFullName}      // using cleanup Function
            >
              <input
                id="fullname"
                placeholder="Enter your full name"
                type="text"
                value={values.fullname}
                onChange={handleChange}
              />
            </OptionalFeature>
         );
    }}
     </Formik>

What I don't like here is that as the for becomes more complex with more OptionalFeatures or more elements nested inside the optional feature, it becomes quite hard to check whether all fields inside the nested optional form are being cleaned up or not. Also the properties of useEffect seem hard to test.

I would prefer some kind of nested subform such that I could write something like onHide={handleReset}, and this would be scoped only to fields inside the nested subform, without me having to define a custom handleReset function for that.

Related