Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input fields lose their focus on change #15

Closed
anuragsr opened this issue Jul 15, 2020 · 9 comments
Closed

Input fields lose their focus on change #15

anuragsr opened this issue Jul 15, 2020 · 9 comments

Comments

@anuragsr
Copy link

I'm using the below code in my App.js:

 <SimpleViewSlider 
       className="outer"
       viewStyle={{ height: "100%" }}
       viewportStyle={{ height: "100%" }}
       innerViewWrapperStyle={{ height: "100%" }}
       >
    <div className="page" key={page}>
         This is view {page}
         {content}
    </div>
 </SimpleViewSlider>

In {content}, I have an input text field
<input type="text" name="email" value={email} onChange={setForm}/>

The issue is, that <div className="page"> is rerendered on change of page every time and that means the inputs lose focus as they are changed.

I'm building a multistep form, pretty much followed the code here, without Material UI. But there doesn't seem to be any input binding to state in the code (useState)

How can I ensure the input fields stay in focus?

@jedwards1211
Copy link
Member

useAutofocusRef is what focuses the inputs in that example. If you use it in a content compnent like

import * as React from 'react'
import { useAutofocusRef } from 'react-transition-context'

const Page = ({ page }) => {
  // this is just an example, you could also manage field values with redux or a form state library
  const [email, setEmail] = React.useState("")
  const handleChange = React.useCallback((e) => setEmail(e.target.value), [
    setEmail,
  ])
  const autofocusRef = useAutofocusRef()

  return (
    <div className="page">
      This is view {page}
      <input
        type="text"
        name="email"
        value={email}
        onChange={handleChange}
        ref={autoocusRef}
      />
    </div>
  )
}

and then later render it like this, it should focus the input.

<SimpleViewSlider
  className="outer"
  viewStyle={{ height: "100%" }}
  viewportStyle={{ height: "100%" }}
  innerViewWrapperStyle={{ height: "100%" }}
>
  <Page key={page} page={page} />
</SimpleViewSlider>

@anuragsr
Copy link
Author

Thanks for the quick reply. Actually I tried using useState within a page component like you have described and the input stays focused, but my issue is that I have a global state for my App which stores values at various steps of my multi-step form.

// Right inside my function App()
const defaultData = { email: "[email protected]" }
const [formData, setForm] = useForm(defaultData) // formData being the global state
// Inside one of the page views (there are many fields on different views)
<input type="text" name="email" value={email} onChange={setForm}/> 

Is there a way to use useState locally like above and update the value from there in the global state? Or a way to avoid re-render of the Page so the input value changes but doesn't lose focus?

@jedwards1211
Copy link
Member

jedwards1211 commented Jul 15, 2020

You should pass the global state and callback down to the input, I was just showing an example with local state. But that isn't actually what's causing the input to lose focus. Are you saying that for example page 1 and page 2 both have email fields and you want the email field on page 2 to be focused after you transition to it from page 1?

@anuragsr
Copy link
Author

anuragsr commented Jul 16, 2020

No, I got that it is done using useAutofocusRef like in your demo. My issue is the re-render of the Page when the global state is updated which (correct me if I'm wrong) causes the input losing focus. I do pass the state and callback as props to the component containing the input, that is how I'm able to update it.

Here's a code sandbox that demonstrates my issue: https://codesandbox.io/s/summer-star-zso9k?file=/src/App.js

Just for info, if you were to implement a state in your demo so that at the last click, the user has all the form values in an object, what would be the approach?

@jedwards1211
Copy link
Member

Thanks for the code sandbox, now I can see the problem: EmailForm and EmailForm2 are remounting on every render because they're set to a new function instance every time App is rendered. If you use the same function instance every time the input won't lose focus. So I'd recommend moving those component functions outside of App. Here's an example of that, you can see it solves the focus problem: https://codesandbox.io/s/youthful-shannon-7fsto?file=/src/App.js

Just for info, if you were to implement a state in your demo so that at the last click, the user has all the form values in an object, what would be the approach?

Well, I was just trying to make the example simple. I don't actually use local state for form values in my projects, I use redux-form. But even redux-form is kind of an old project and its author made a more modern library for form state called react-final-form 😅.

If you want to use either of those libraries they have examples of how to implement this type of form:

react-final-form multi-step form: https://final-form.org/docs/react-final-form/examples/wizard
redux-form multi-step form: https://redux-form.com/8.3.0/examples/wizard/

@jedwards1211
Copy link
Member

jedwards1211 commented Jul 16, 2020

Here's an example of what I mean about different function instances each time:

function App() {
    const EmailForm = () => {}
    return EmailForm
}
const emailFormA = App()
const emailFormB = App()
console.log(emailFormA === emailFormB) // false

Unfortunately, even though the code in EmailForm hasn't changed, JS creates a new function instance for it. If JS returned the same function instance every time, on the second render EmailForm would be bound to the old value of formData (in your example), not the new value.

When you render <EmailForm ... />, if EmailForm is !== what it was on the last render, React will remount it (remove all the DOM elements from the previous render and replace them with new DOM elements). This remounting was what caused the input to lose focus.

@anuragsr
Copy link
Author

Oh I understand it now! Thanks a lot for taking the time to explain, and providing relevant links. Great library btw!

@jedwards1211
Copy link
Member

Thanks! Hope it goes well implementing the form, to me forms still feel more tedious to implement than they should be, at least with redux-form.

@anuragsr
Copy link
Author

I agree compared to Angular and Vue for example..they are indeed more tedious

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants