jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
33.97k stars 2.79k forks source link

Need a more complete example of innerRef usage #2287

Open rjray opened 4 years ago

rjray commented 4 years ago

📖 Documentation

Currently, the innerRef property is documented under <Field />. However, putting it there results in a warning to the console. Other readings indicate that it should be passed to the <Formik /> element. But there is no indication of how to then use the ref to access actual form DOM elements (such as to set focus on a specific field).

rjray commented 4 years ago

For reference, this is the warning I get:

Warning: React does not recognize the `innerRef` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `innerref` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
    in input (created by FormControl)
    in FormControl (created by Field)
    in Field (at MagazineForm.js:73)
    in div (created by Col)
    in Col (at MagazineForm.js:72)
    in div (created by FormRow)
    in FormRow (created by FormGroup)
    in FormGroup (at MagazineForm.js:67)
    in form (created by Form)
    in Form (at MagazineForm.js:66)
    in Formik (at MagazineForm.js:54)
    in MagazineForm (at MagazineUpdate.js:59)
    in div (created by Container)
    in Container (at MagazineUpdate.js:69)
    in MagazineUpdate (at Magazines.js:13)
    in Magazines (created by Context.Consumer)
    in Route (at App.js:34)
    in Switch (at App.js:20)
    in div (created by Container)
    in Container (at App.js:19)
    in Router (created by BrowserRouter)
    in BrowserRouter (at App.js:12)
    in App (at src/index.js:10)

The use of innerRef in the <Field /> component was:

<Field
    ...
    innerRef={input => { saveRef = input }}
    ....>
etc.
johnrom commented 4 years ago

@rjray you're using a custom component which means you'll receive innerRef as a prop instead of ref. Not sure the reasoning behind that, but we would pass ref itself when #2208 lands (probably not until TS + forwardRef work with generics, or we can ignore a specific TS error message). Until then, your component should look like:

// and whatever other props you want to separate
const FormControl = ({ innerRef, ...rest }) => (
    <input ref={innerRef} {...rest} />
);
MannySauce commented 4 years ago

@johnrom Hi, im kind of in the same situation im trying to make it so when the 'next' button in keyboard is pressed the next input is focused so i thought i'd need refs for that. Here I created Refs in the constructor, and tried to give it to the input but it gives undefined errors... How would your example look like using this code:

<Field
   name="email"
   label="email"
   component={Input}
   onChangeText={formikProps.handleChange('email')}
   value={formikProps.values.email}
   keyboardType="email-address"
   touched={formikProps.touched.email}
   errors={formikProps.errors.email}
   returnKeyType="next"
   onSubmitEditing={() => this.current.passwordInput.focus()}
   innerRef={ (input) => { this.emailInput = input }}
   onBlur={formikProps.handleBlur('email')}
/>
rjray commented 4 years ago

Well, almost nine months later and I've finally cracked this. Funny-enough, when I sat down to try this again and Googled for "react formik innerref" this issue was the first search result presented. Always fun when the first search-hit is your own original question...

But anyway.

My problem was that I am using a custom component (<Form.Control />) that I don't have control over; it's part of React Bootstrap. But reading the answer above from @johnrom suddenly clicked. So I did this:

const FocusFormControl = ({ innerRef, ...props }) => (
  <Form.Control ref={innerRef} {...props} />
);

I then changed the <Field /> invocation to:

<Field
  as={FocusFormControl}
  onBlur={handleBlur}
  onChange={handleChange}
  type="text"
  name="name"
  innerRef={(el) => (autoFocusRef.current = el)}
  placeholder="Name"
  autoFocus
  data-lpignore="true"
/>

Here, autoFocusRef was a prop passed in to my form-creation component. No errors, and no spew in the console.

For the sake of helping @MannySAD and anyone else who comes across this, I used this in conjunction with a custom hook that I gleaned from this StackOverflow question (look for the high-score answer). Here is the hook:

import { useRef } from "react";

export const useFocus = () => {
  const htmlElRef = useRef(null);
  const setFocus = () => {
    htmlElRef.current && htmlElRef.current.focus();
  };

  return [htmlElRef, setFocus];
};

And then:

const [focus, setFocus] = useFocus();

...

<TagForm
  submitHandler={submitHandler}
  tag={{ name: "", description: "", type: "" }}
  autoFocusRef={focus}
/>

Then a call to setFocus() when I need to re-focus to the main input.

samyoungnyc commented 3 years ago

Hi - I'm trying to autofocus on the first input in my form. AFAICT I'm using default Formik elements, not customizing them in any way. This form is in a React Function component.

<Formik
        initialValues={{ displayName: '',  email: '', password: '' }}
        onSubmit=...
      >
        <Form>
           <Field
              name="displayName"
              type="text"
              placeholder="username"
              // set focus on this field
          />
        ....

I've tried a bunch of ways based on the answers (including from the SO link) above to make this work but am utterly confused. In your (@rjray) example, I see that you make a hook, then (I think) you use that hook to set a custom prop you've added to a customized Formik element (?).

I've tried your approach (creating the hook, however since my Form components aren't custom, I've opted to try using the following props on the <Field /> component, based on a number of SO and other types of posts I've seen - ref, innerRef, refs.

None of these seem to work, either crashing altogether or simply not focusing the Field.

To summarize, I'm using the following default Formik elements in a React Function component:

<Formik>, <Form>, <Field>

I would like to focus the <Field> element, when the Component first renders.

If there is any way anyone can clarify this, I would really appreciate it.

johnrom commented 3 years ago

@samyoungnyc if you can provide a codesandbox repo I can help you with this.

You should be able to simply do the following; remove the Typescript if you don't use it.

const MyForm = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <Formik {...formikProps>
    <Form>
      <Field name="myField" ref={inputRef} />
    </Form>
  </Formik>
}

Using a plain field does not use innerRef. Formik should, in theory, just pass along the plain ref to a plain input. InnerRef is used for custom components like component={MyComponent} or as={MyComponent}. If you tried to pass a ref to those, TypeScript would error out.

samyoungnyc commented 3 years ago

Hi @johnrom - thanks for responding. Apologies for late reply but I tabled this issue for another time. In any case, I've made a codesandbox and fiddled around some more.

I replicated your code (in plain React/JS).

No errors were thrown however the input element didn't focus (in my copy of your component - non-TS).

If I remove the ? from inputRef.current?.focus(); - the following error occurs:

Cannot read property 'focus' of null

I did a little more digging - and realized that my component structure was the larger part of the issue. I had 2 components (login + signup) that were children of a parent component (2 tabs to choose + render either form). The first tab was login, and the 2nd was sign up (where I wanted the focus). I realized however that both login+signup components were being rendered within my two-tab structure, so switching to the 2nd tab, did not re-render, and thus the ref wasn't being focused (this I don't understand fully).

Finally, using ref wasn't working so I switched the order of my tabs so that the signup form was first, and used innerRef instead of ref and it finally worked.

In any case, if you have just one form - the following should work:

import React, { useRef, useEffect } from "react";
import "./styles.css";
import { Formik, Field, Form, ErrorMessage } from "formik";

const MyForm = () => {
  const inputRef = useRef(null);

  useEffect(() => {
    return inputRef.current?.focus();
  }, []);

  return (
      <Formik>
        <Form>
          <label>name: </label>
          <Field name="myField" innerRef={inputRef} />
        </Form>
      </Formik>
    )
  }
export default MyForm;

Hope this helps someone in the future, and thanks for the feedback!

Johnrobmiller commented 3 years ago

I'm glad people are being helpful and providing solutions... but, did they docs get improved? Because that's partially what the OP was all about. Also, improving the docs will help WAY more people than posting solutions in this thread.

gitsunaina commented 8 months ago

Hi - I'm trying to autofocus on the first input in my form. AFAICT I'm using default Formik elements, not customizing them in any way. This form is in a React Function component.

<Formik
        initialValues={{ displayName: '',  email: '', password: '' }}
        onSubmit=...
      >
        <Form>
           <Field
              name="displayName"
              type="text"
              placeholder="username"
              // set focus on this field
          />
        ....

I've tried a bunch of ways based on the answers (including from the SO link) above to make this work but am utterly confused. In your (@rjray) example, I see that you make a hook, then (I think) you use that hook to set a custom prop you've added to a customized Formik element (?).

I've tried your approach (creating the hook, however since my Form components aren't custom, I've opted to try using the following props on the <Field /> component, based on a number of SO and other types of posts I've seen - ref, innerRef, refs.

None of these seem to work, either crashing altogether or simply not focusing the Field.

To summarize, I'm using the following default Formik elements in a React Function component:

<Formik>, <Form>, <Field>

I would like to focus the <Field> element, when the Component first renders.

If there is any way anyone can clarify this, I would really appreciate it.

use innerRef props inside component