2

I am working on an application that will feature speech to text commands. Currently I have a form made - but I want to try and control the input elements from outside of the form framework.

<form id="myform">
  <input type="text" name="foo" />
</form>

enter image description here

I have followed this basic example and I can gain access to the input elements and change the value.

document.getElementById("myform").elements["foo"]
document.getElementById("myform").elements["foo"].value = "Peter Pan" 

I've created a fiddle here https://codesandbox.io/s/loving-waterfall-n3g4mt?file=/src/App.js

Best Practice: Access form elements by HTML id or name attribute?

but when I try and get access to formik form elements I am unable to find them using this method, let alone change values. I am not even sure how to even set an id on the form tag that formik makes -- this didnt have any impact so I had to set it around a div wrapper for now.

<Formik
   initialValues={"x"}
   validationSchema= {"x"}
   onSubmit={this.submitMyForm}
   innerRef={this.visitFormRef}
   id={"myform3"}
>

http://jsfiddle.net/3bsdn8yz/1/

<div id="myform3" class="general-formik standard-padding ">
   <form action="#">
      <div class="field field-comment">
         <div class="MuiFormControl-root MuiFormControl-fullWidth MuiTextField-root css-wb57ya-MuiFormControl-root-MuiTextField-root">
            <label class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-colorPrimary Mui-error MuiFormLabel-filled MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined css-1sumxir-MuiFormLabel-root-MuiInputLabel-root" data-shrink="true" for="mui-59" id="mui-59-label">Describe your symptoms here</label>
            <div class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary Mui-error MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-multiline css-8ewcdo-MuiInputBase-root-MuiOutlinedInput-root">
               <textarea rows="6" aria-invalid="true" autocomplete="off" name="describe_your_symptoms_here" placeholder="Text field" maxlength="700" class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMultiline css-1sqnrkk-MuiInputBase-input-MuiOutlinedInput-input" id="mui-59" style="height: 138px;">xxx</textarea>
               <textarea aria-hidden="true" class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMultiline css-1sqnrkk-MuiInputBase-input-MuiOutlinedInput-input" readonly="" tabindex="-1" style="visibility: hidden; position: absolute; overflow: hidden; height: 0px; top: 0px; left: 0px; transform: translateZ(0px); padding: 0px; width: 1115px;"></textarea>
               <fieldset aria-hidden="true" class="MuiOutlinedInput-notchedOutline css-1d3z3hw-MuiOutlinedInput-notchedOutline">
                  <legend class="css-14lo706"><span>Describe your symptoms here</span></legend>
               </fieldset>
            </div>
         </div>
         <p class="MuiFormHelperText-root Mui-error css-1d1r5q-MuiFormHelperText-root">Must be at least 8 characters</p>
      </div>
      <div class="button-wrapper"><button class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium css-sghohy-MuiButtonBase-root-MuiButton-root" tabindex="0" type="reset">Reset<span class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"></span></button><button class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedSecondary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-root MuiButton-contained MuiButton-containedSecondary MuiButton-sizeMedium MuiButton-containedSizeMedium css-zcbmsk-MuiButtonBase-root-MuiButton-root" tabindex="0" type="submit">Submit<span class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"></span></button></div>
   </form>
</div>

9th June 2023 I managed to get the value to change - but it reverted back to the formik object.

document.getElementsByName("describe_your_symptoms_here")[0].value = "Roger Rabbit"

How to update Formik Field from external actions

other research

How do I change a Formik TextField value from the outside of the From?

  • I think he creates a function outside the framework and feeds it into formik at a particular place.

"Formik render method provides a prop to change the field value manually using setFieldValue prop it takes the field name & new values as parameters you can read about if more from here

As for what you need to change is here"

// accept a new parameter which you can pass to the `analyzeQuaggaFile` function
function inputFile(setFieldValue) {
  const file = document.getElementById("quaggaFile").files[0];

  const reader = new FileReader();

  reader.addEventListener(
    "load",
    function () {
      // pass the setFieldValue
      analyzeQuaggaFile(reader.result, setFieldValue);
    },
    false
  );

  reader.readAsDataURL(file);
}

// second parameter is setFieldValue
function analyzeQuaggaFile(src, setFieldValue) {
  Quagga.decodeSingle(
    {
      src: src,
      numOfWorkers: 0,
      inputStream: {
        size: 800,
      },
      decoder: {
        readers: ["ean_reader"],
      },
    },
    function (result) {
      if (result.codeResult) {
        // update the isbn field value
        setFieldValue("isbn", result.codeResult.code);
      } else {
        console.log("not detected");
      }
    }
  );
}

Now change this in your JSX code:

<Formik initialValues={{ name: "" }} onSubmit={handleSubmit}>
  {({ errors, isSubmitting, setFieldValue }) => (
    <Form className={classes.root}>
      {/* Other fields */}
      Scan bar code:
      <input
        id="quaggaFile"
        type="file"
        accept="image/*"
        capture="camera"
        onChange={() => inputFile(setFieldValue)} // pass the setFieldValue property from formik 
      />
      {/* Other fields */}
    </Form>
  )}
</Formik>;

https://github.com/jaredpalmer/formik/issues/229 How to update Formik Field from external actions

General Grievance
  • 4,555
  • 31
  • 31
  • 45
The Old County
  • 89
  • 13
  • 59
  • 129
  • Can you make an example here? https://codesandbox.io/s/react-new – Paulo Fernando Jun 09 '23 at 16:40
  • I will try - my form framework is quite dense. Its just outside the form framework - this requested manipulation. If it were a normal HTML form it may be easier - but there are formik handlers but inside the formframework I've made here in nested components - not sure how easy it is to squirt field value changes into the component for it to find and absorb – The Old County Jun 09 '23 at 17:31
  • https://codesandbox.io/s/loving-waterfall-n3g4mt?file=/src/App.js – The Old County Jun 09 '23 at 20:34

1 Answers1

1

It's not possible to change using document.getElementById, even being able to actually change the HTML, the changes would not affect the component, you need to use the Formik built in setFieldValue. I created a ref and passed it down to the Formik so I could get the reference to this method. I also added a global event listener to be able to change this from anywhere, not just inside react code.

For example go to https://l6mssp.csb.app/ and run this code in the console:

window.dispatchEvent(
      new CustomEvent("formik_external_change", {
        detail: [
          { name: "wound_opening", value: ['0'] },
          { name: "pain_level", value: 4 },
          { name: "healthy_regrowth_of_skin", value: ['0'] },
          { name: "discolouration", value: 3 },
          { name: "swelling", value: 2 },
          { name: "describe_your_symptoms_here", value: "Feeling dizzy" }
        ]
      })
    );

https://codesandbox.io/s/nameless-framework-l6mssp

GeneralFormik.js

add the marked line (around line 129):

return (
      <div id={this.props.schema.id} className={"general-formik standard-padding " + getFormType()}>
        <Formik
          initialValues={getInitialValues(this.props.schema.initialValues, this.props.schema.fields)}
          validationSchema={createYupSchema(this.props.schema.fields)}
          onSubmit={this.submitMyForm}
          innerRef={this.visitFormRef}
        >
          {(props) => {
                 if (this.props.externalControlRef) {
                      this.props.externalControlRef.current = props.setFieldValue; //  assigning the method to the external ref
                 }
            return (
              <Form id={this.props.id} onKeyDown={this.onKeyDown}>
                <FieldMaker schema={this.props.schema} values={props.values} fieldChanged={this.fieldChanged} onErrorHandle={this.onErrorHandle} />
                <ButtonMaker schema={this.props.schema} />
              </Form>
            )
          }
          }
        </Formik>
      </div>
    )
  }

App.js

After declaring the schema:

 //  Creating the ref
  const externalControlRef = useRef();

  //  Creating a function to be able to call it with a nice name
  const setFormValues = (jsonArray) => {
    jsonArray.forEach(field => externalControlRef.current(field.name, field.value))
  }

  //  Global listener for the custom event "formik_external_change"
  useEffect(() => {
    const handleExternalFormikChange = (e) => {
      setFormValues(e.detail);
    }

    window.addEventListener("formik_external_change", handleExternalFormikChange);

    //  Need to remove the event listener on component unload otherwise it would have multiple events
    return () => window.removeEventListener("formik_external_change", handleExternalFormikChange);
  }, [])

  //  Triggering with a button but the code inside this can be called from anywhere (console or the text to speech library for example)
  const handleChange = () => {
    window.dispatchEvent(
      new CustomEvent("formik_external_change", {
        detail: [
          { name: "wound_opening", value: ['0'] },
          { name: "pain_level", value: 4 },
          { name: "healthy_regrowth_of_skin", value: ['0'] },
          { name: "discolouration", value: 3 },
          { name: "swelling", value: 2 },
          { name: "describe_your_symptoms_here", value: "Feeling dizzy" }
        ]
      })
    );
  }

  return (
    <div className="App">
      {/* //  Click to change! */}
      <button onClick={handleChange}>Check skin and change pain </button>

      <GeneralFormik
        externalControlRef={externalControlRef} //  Passing the ref down
        schema={schema3}
        submitHandler={function (data) {
          console.log("new data in parent", data);
        }}
      />
    </div>
  );
Paulo Fernando
  • 3,148
  • 3
  • 5
  • 21
  • How does externalControlRef connect to the form - you mean you made the ref from the parent to the child - but then how do your sefFields work - is this working in the code sandbox? Its going to be a voice controlled system - so we'd need to setFields from an ever changing blob of json being generated by the speech-to-text-commander https://codesandbox.io/s/loving-waterfall-n3g4mt?file=/src/App.js – The Old County Jun 10 '23 at 01:13
  • 1
    It connects in line 133 of GeneralFormik.js : https://codesandbox.io/s/nameless-framework-l6mssp I dont know how the text to speech commander works, but i gave you an example of how to change the values from outside of the form, there are 2 buttons in the begining of the page. If you can make a sandbox with this text to speech commander so i can understand it better... – Paulo Fernando Jun 10 '23 at 04:55
  • So where you have got setFieldValue - - I would need to squirt a changing blob of json of key/value pairs -- to provide what field(s) to set -- so how would you push a json array/object with key/value pairs -- into handleChange - and would it be able to detect the object has changed -how would you trigger it – The Old County Jun 10 '23 at 12:55
  • changePainLevel and changeSkin -- make them more generic setField functions. let = newChange [{ "key": "pain_level", "value": 4 }] changeFields(newChange) – The Old County Jun 10 '23 at 12:57
  • I will try to apply that, but do you think you you can make a separate example only with this ever changing blob json? – Paulo Fernando Jun 10 '23 at 13:51
  • I'm not sure what you mean. Like a person would speak -and the words get converted into text - "I want to change my wound status to red" -- we would have to use regular expressions to fish out the name of the field - aka wound status - and the value and check its a valid option -- aka red -- and make a small data blob as shown - and trigger the changeFields func with the data blob. So a person would make continuous commands that way. – The Old County Jun 10 '23 at 15:42
  • I think i got the point, check the new changes, you can trigger an event from anywhere, passing an array of fields. – Paulo Fernando Jun 10 '23 at 17:26
  • Ok great - is the GeneralFormik component stable if this externalControlRef is missing aka it wont be part of the props for old plain forms - standard mode. Its a conditional thing. – The Old County Jun 11 '23 at 01:53
  • 1
    Good point It Will not be the way It is. You should wrap that line in an `if (this.props.externalControlRef) ` – Paulo Fernando Jun 11 '23 at 03:39
  • Are you able to provide a version where we dont use hooks - where maybe the form is part of a class component? – The Old County Jun 26 '23 at 22:42
  • On the button click - handleChange(detail) -- could we push the changes we want to make like this - instead of hard coding the changes inside the function itself? onClick={()=> handleChange(detail2)} – The Old County Jun 26 '23 at 22:56
  • I didn't understand, to not use hooks? Which hooks are being used now? – Paulo Fernando Jun 27 '23 at 01:10
  • About the button click handleChange, this is Just an example you can use the windows.dispatchEvent from anywhere, passing any prop – Paulo Fernando Jun 27 '23 at 01:13
  • I got some old code that is half baked on class components – The Old County Jun 27 '23 at 03:41
  • Created a new post - https://stackoverflow.com/questions/76568294/external-control-of-formik-form-inside-class-components - thank you -- its the same setup - just instead of generalform component being inside a class component where you make and pass it the external control ref – The Old County Jun 27 '23 at 20:07