igo
igo

Reputation: 6858

Focus input after redux dispatch finished

I am dynamically creating list of inputs with React and Redux. After clicking a button an input is added to the end of list. I need to focus last added input. I tried this code but it focuses penultimate input

const mapDispatchToProps = (dispatch, ownProps) => ({
    onOptionsChange: (newOptions) => {
        dispatch(formActions.updateOptions(newOptions));
    }
});

...
this.props.onOptionsChange({ ...this.props, inputsList}); // change list of inputs
ReactDOM.findDOMNode(this.inputs[this.props.choices.length - 1]).focus();

In logs I can see that focus() is executed before props from state are updated. How can I wait for dispatch to finish?

Upvotes: 16

Views: 2348

Answers (4)

FlatLander
FlatLander

Reputation: 1767

Promisify your dispatch

Lets write a middleware that returns a promise after completing the dispatch.

const thenMiddleware = store => next => action => {
    return new Promise((resolve, reject) => {
        try {
            resolve(next(action));
        } catch(e) {
            reject(e);
        }
    })
};

Prepare your store first, by installing the middleware

import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers/index';

/**
 * Our star of the show
 */
const thenMiddleware = store => next => action => {
    return new Promise((resolve, reject) => {
        try {
            resolve(next(action));
        } catch(e) {
            reject(e);
        }
    })
};

const store = createStore(
  rootReducer,
  applyMiddleware(thenMiddleware)
);

Now we have to return the dispatch inside our mapDispatchToProps method

const mapDispatchToProps = (dispatch, ownProps) => ({
  onOptionsChange: (newOptions) => {
    return dispatch(formActions.updateOptions(newOptions));
  }
});

Finally hook your event on complete

Now that you know which input you created last time, you can attach an on complete function to this dispatch.

this.props.onOptionsChange(newOptions).then(() => {
  // do your input focusing here
});

Upvotes: 4

Arpit
Arpit

Reputation: 438

I faced a similiar problem, wherein, I had a list of items and I need to focus on the last element whenever a new item is added. The below code is using flux architecture but i guess it should solve your problem.

addNewItem: function() {
    var self = this;
    var list = this.state.list || {};
    var listOrder = this.state.listOrder || [];
    var newKey = "some_random_key";

    list[newKey] = {
        "product": ""
    };
    listOrder.push(newKey);
    this.state.list = list;
    this.state.listOrder = listOrder;
    this.setState({list: this.state.list, listOrder: this.state.listOrder, showSaveButton: true});
    setTimeout(function(){
        $(".buy-form-item-list-row .buy-form-item-list-item-name").last().focus();
    }, 0);
},

So in the above code.. listOrder contains the keys of list of items. Eg: list is an object which contain the item. Eg:

listOrder=[ran_1, ran_2];

list={ ran_1: { product: "Eggs - 5" }, ran_2: { product: "Bread - 1" } }

render method, iterate over the listOrder and creates a Input tag with className="buy-form-item-list-item-name" for each item.

The trick to focus on the last item is the setTimeout with an interval of 0. When you call setState it does not trigger render function at that moment itself, instead it waits for the function to execute completely.

Hope this helps.

Upvotes: -1

Hulvej
Hulvej

Reputation: 4205

I would implement componentDidUpdate and check the length of your "input-array" or whatever data-structure you are using:

componentDidUpdate(prevProps, prevState) {
   if (prevProps.choices.length < this.props.choices.length) {
      ReactDOM.findDOMNode(this.inputs[this.props.choices.length - 1]).focus();
   }
}

Upvotes: 2

Andreyco
Andreyco

Reputation: 22872

Create new wrapping component for input element. That component will autofocus, when rendered.

class Input extends React.Component {
    keepRef = (ref) => {
        this.ref = ref;
    }

    componentDidMount() {
        this.ref.focus();
    }

    render () {
        return (
            <input ref={this.keepRef} {...this.props} />
        )
    }
}

If you don't want Input component to handle focus, move logic to it's parent.

Upvotes: 0

Related Questions