Search code examples
reactjsinputformattingformikcleave

How to use cleave.js as a Formik field?


I'd like to use a Cleave (see https://nosir.github.io/cleave.js/ for details) as a Field inside a Formik form. While built-in components like text inputs work fine, the Cleave value change is not recorded and furthermore reset if any other value is changed in the form.

Maybe there's a good explanation why this is a bad idea. I'm confused by the following setup not working out-of-the-box. I expect the value to be not reset and stored in the form's values which are eventually submitted.

I'm using the following code:

import React from "react";
import { Formik, Form, Field, ErrorMessage } from "formik";
import Cleave from 'cleave.js/react';

class App extends React.Component {

  render() {
    return <div>
      <Formik
        initialValues={{ title: "", price: 0 }}
        validate={values => {
          this.setState({ validationErrorDetails: null, errorMessage: "" });
          let errors = {title: "", price: ""};
          console.log("validate values", values);
          if (!values.price || isNaN(values.price)) {
            errors.price = "Price amount is required";
          }
          return errors;
        }}
        onSubmit={values => {
          alert(JSON.stringify(values));
        }}
        render={({ isSubmitting, handleSubmit, handleChange, handleBlur, values }) => (
          <Form>
            <table>
              <tbody>
                <tr>
                  <td>
                    <label>Title:</label>
                  </td>
                  <td>
                    <Field name="title" component="input" />
                  </td>
                  <td>
                    <ErrorMessage name="title" component="div" />
                  </td>
                </tr>
                <tr>
                  <td>
                    <label>Price:</label>
                  </td>
                  <td>
                    <Field name="price" component={() => <Cleave value={values.price}
                          options={{numericOnly: true, numeral: true, numeralThousandsGroupStyle: "thousand"}} />}/>
                  </td>
                  <td>
                    <ErrorMessage name="price" component="div" />
                  </td>
                </tr>
              </tbody>
            </table>
            <button type="submit" disabled={isSubmitting} className="confirm-button">
              Submit
            </button>
          </Form>
        )}/>
    </div>;
  }
}

export default App;

and just ReactDOM.render(<App />, document.getElementById('root')) on the index page. An SSCCE providing the boilerplate, but not more logic is provided at https://gitlab.com/krichter/react-formik-with-cleave.


Solution

  • Formik will not magically bind handleChange to a <Cleave> element like it does for <Field>. You'll need to bind it yourself like this:

    <Cleave value={values.price}
            options={...}
            onChange={handleChange}
    />
    

    Cleave onChange events have both the display value and raw value (e.g. {value: $1,000, rawvalue: 1000}).

    I'm assuming for most implementations you'd want the raw value passed to Formik, so you'll need to add a custom event to the <Cleave> component.

    <Cleave value={values.price}
            options={...}    
            onChange={event => {
                const tempEvent = event
                tempEvent.target.value = event.target.rawValue
                handleChange(tempEvent)
            }}
    />