Redux forms: Scroll to form field containing error message

  • Redux forms v6.2+ provide you with an onSubmitFail callback when a validation fails in your form.
  • Add refs to all Field elements with name
  • Note: Without refs, this will not work at all.
  • Import the following function as a utility and bind it in your constructor.
  • Whenever there is an error, this scroll the page to the 1st element in the page containing errors.

import ReactDOM from 'react-dom';
/**
 * Handles the errors received and determines the first element
 * containing the error as the 'key' element, so that,
 * it's positions can be determined
 * Scrolls the page that element to bring into visibility
 * And focuses on the element
 * Requires developers to use 'ref' attribute with 'name' in form fields
 * @param  {[object]} errors
 * @return {[undefined]}
 */

export function handleSubmitFail(errors) {
    if (!this.refs) {
        return;
    }
    const refsKeys = Object.keys(this.refs);
    const keys = Object.keys(errors);
    let key = null;
    let matchfound = false;

    refsKeys.filter(item => {
        if (keys.indexOf(item) > -1 && !matchfound) {
            key = item;
            matchfound = true;
            return false;
        } else { // eslint-disable-line no-else-return
            return true;
        }
    });

    this.targetNode = this.refs[key];

    if (this.targetNode) {
        const node = ReactDOM.findDOMNode(this.targetNode);
        const parentNode = ReactDOM.findDOMNode(this);
        const xy = node.getBoundingClientRect();
        this.x = xy.right + window.scrollX;
        this.y = xy.top + window.scrollY - 60;
        parentNode && parentNode.scrollTo && parentNode.scrollTo(this.x, this.y) || 
        (parentNode && parentNode.scrollTop && (parentNode.scrollTop = this.y) ) || 
        window && window.scrollTo(this.x, this.y); // eslint-disable-line no-unused-expressions
    }
}

Update: The section below has been added as per the request of John, who made a comment requesting me to explain how to use this.

How to use it?

To use this handleSubmitFail event handler, we first need to save it somewhere. So, let’s save it under a `utils` directory.

Now, we need to import this method into each of our components. There might be other simpler ways than just writing the same code over-n-over again, but we won’t be getting into that right now.

After importing, we’ve to bind it with the component’s this lexicon in the constructor.


import React, { Component } from 'react';
import { reduxForm, propTypes } from 'redux-form';
import { connect } from 'react-redux';
import { handleSubmitFail } from 'utils/errorUtils';
import * from '../../../every/thing/else';

let onSubmitFailHandler = undefined;

/**
 * Let's define some component here.
 */
class SomeComponent extends Component {
    static propTypes = {
        ...propTypes,
        onSubmit: React.PropTypes.func.isRequired,
        initialState: React.PropTypes.object.isRequired,
        onDiscard: React.PropTypes.func.isRequired,
        currentFormState: React.PropTypes.object
        ...
    };

    constructor(props) {
        super(props);
        // this is where we bind the handleSubmitFail method
        // which we imported from the Utils directory
        onSubmitFailHandler = handleSubmitFail.bind(this);
    }

    shouldComponentUpdate(nextProps) {
        if ((this.props.initialState !== nextProps.initialState) ||
            (this.props.currentFormState !== nextProps.currentFormState) ||
            (this.props.loading !== nextProps.loading)) {
            return true;
        }
        return false;
    }

    componentDidMount() {
        this.props.executeSomeMethod();
        this.props.executeAnotherMethod();
    }

    componentWillReceiveProps(nextProps) {
        // execute all methods you want to
    }

    componentWillUnmount() {
        // ...
    }

    /**
     * On submit calls onSubmit of redux form.
     */
    handleSubmit = (form) => {
        this.props.onSubmit(form);
    }

    render() {
        if (this.props.loading) {
            return ;
        }

        return (
<SomeComponentChildElement>
    <h1 className='form-heading'>Please fill up the Form:</h1>
    <form autoComplete={ 'off'}>
        <Field name='account_name' ref='account_name' component={Fields.AdvertiserNameField}/>
        <Fields.EmailField name='primary_email' ref='primary_email' />
        <Fields.PhoneField name='primary_phone' ref='primary_phone' />
        <Fields.CommunicationAddressField name='line_one' ref='line_one' />
    </form>
    
</Paper> </SomeComponentChildElement> ); // JSX template goes here } } const formName = 'someComponentName'; export default connect(state => ({ initialValues: state.some.place.form.someComponent, initialState: state.some.place.form, loading: state.some.place.form.meta.someComponentLoading, currentFormState: state.form[formName] }), dispatch => ({ onSubmit: (form) => dispatch(handleSubmitForSomeComponent(form)), loadingSomeComponent () => dispatch(loadingSomeComponent()), loadedSomeComponent: () => dispatch(loadedSomeComponent()), handleUnmount: (form, isFormValid) => dispatch(handleSomeComponentUnmounted(form, isFormValid)) }))(reduxForm({ form: formName, validate, onSubmitFail: (errors) => { if (onSubmitFailHandler) { onSubmitFailHandler(errors); } else { throw new Error('No handlers found for redux-form submission failure! Look at http://redux-form.com/5.2.3/#/api/reduxForm?_k=nz53ec for more info.'); } } })(SomeComponent));
Advertisements