How to handle focus using declarative/functional style libraries like Redux and ReactJS?
Asked Answered
S

1

10

In looking around to see what ways other developers are handling input focus when working with Redux I've come across some general guidance for ReactJS components such as this. My concern however is that the focus() function is imperative and I could see strange behaviours possible where multiple components are fighting over focus. Is there a redux way of dealing with focus? Is anybody dealing with pragmatically setting focus using redux and react and if so what techniques do you use?

Related:

Sanctimony answered 17/1, 2016 at 18:8 Comment(4)
Redux and DOM nodes / DOM functions, have nothing to do with each other really... not sure what how you're expecting Redux to be involved. Why would multiple components be fighting over focus, and what kind of strange behaviours are you trying to avoid?Elisabeth
As azium said redux is just a way of managing and storing app state. So perhaps you should re-word your question - are you asking how to manage multiple components which have focus() set and when they are rendered on a page together you want to decide which one actually gets the focus? I guess that would depend entirely on your app! It'd by default be the one that was rendered last I guess. Also consider the autofocus attribute.Lavenialaver
Dominic, you describe my concerns pretty well. Multiple components intending to set focus but only one will win. In the end the rendered view will not be in sync with the declarative representation, partly because with a call to focus() it's no longer declarative and also because the act of gaining focus causes side effects.Sanctimony
I tried autofocus but for some reason it didn't work but I haven't gotten a chance to look into why very deeply.Sanctimony
S
8

My approach is using ref callback, which is kind of an onRenderComplete of an element. In that callback I can focus (conditionally, if needed) and gain a reference for future focusing.

If the input is rendered conditionally after an action runs, that ref callback should fire a focus, because the ref doesn't exist yet immediately after calling the action, but only after render is done. Dealing with componentDidUpdate for things like focus just seems like a mess.

// Composer.jsx -- contains an input that will need to be focused somewhere else

class Composer extends Component {
  render() {
    return <input type="text" ref="input" />
  }

  // exposed as a public method
  focus() {
    this.refs.input.focus()
  }
}

// App.jsx

@connect(
  state => ({ isComposing: state.isComposing }),
  ...
)
class App extends Component {
  render() {
    const { isComposing } = this.props // or props, doesn't matter
    return (
      <div>
        <button onClick={::this._onCompose}>Compose</button>
        {isComposing ? <Composer ref={c => {
          this._composer = c
          this._composer && this._composer.focus() // issue initial focus
        }} /> : null}
      </div>
    )
  }

  _onCompose() {
    this.props.startComposing() // fire an action that changes state.isComposing

    // the first time the action dispatches, this._composer is still null, so the ref takes care of the focus. After the render, the ref remains so it can be accessed:

    this._composer && this._composer.focus() // focus if ref already exists
  }
}

Why not autoFocus or isFocued prop?

As HTMLInputElement has value as a prop, but focus() as a method -- and not isFocused prop -- I would keep using methods to handle that. isFocused can get a value but if the user blurs from the input, what happens to that value? It'll be out of sync. Also, as mentioned in the comments, autoFocus can conflict with multiple components

So how to decide between props and methods?

For most cases props will be the answer. Methods can be used only in a 'fire and forget' things, such as scrollToBottom in a chat when a new message comes in, scrollIntoView and such. These are one time behaviors that the store doesn't care about and the user can change with an interaction, so a boolean prop won't fit. For all other things, I'd go with props.

Here's a jsbin:

http://jsbin.com/waholo/edit?html,js,output

Singleton answered 28/1, 2016 at 1:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.