Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux-form: display a list of errors on top of a page

Tags:

I want to use Redux-form in a manner that changes input color & displays the actual error on top of the page. How do I access the list of current field errors outside the fields themselves?

like image 356
Capuchin Avatar asked Sep 06 '16 10:09

Capuchin


2 Answers

You can't get the list of errors from outside of the render function given to the Field component. This is because errors are not stored in the redux store.

EDIT 26/12/2018 :

This answer is taking some age. ReduxForm now stores errors in the Redux store. Take a look to @nicoqh's answer which is using ReduxForm's selectors to get errors in any Redux connected component.

This answer is not totaly obsolete but it's not anymore the cleanest way to achieve this imho.

Solution 1: Use multiple Field for the same value

The first solution is to use multiple instance of Field for the same value. If multiple Field components have the same name and is connected to the same form name, they will all be connected to the same value and the same error handling.

So you can use a Field component and only render the error.

import React from 'react'
import {reduxForm} from 'redux-form'

const renderError = ({input, meta, ...props}) => (
    <span {...props} className='error'>Error : {meta.error}</span>
)

const renderInput = ({input, meta, ...props}) => (
    <input {...input} {...props} className={meta.error ? 'error' : null} />
)

const FormWithError = ({handleSubmit}) => (
    <div>
        <div className='errorContainer'>
            <Field name='myInput' component={renderError} />
        </div>
        <form onSubmit={handleSubmit}>
            <Field name='myInput' component={renderInput} />
        </form>
    </div>
)

const validate = (values, props) => {
    const errors = {}
    /* calculate errors here by appending theim to errors object */
    return errors
}

export default reduxForm({form: 'myForm', validate})(FormWithError)

Solution 2: Use the global error prop

A second solution is to use the global error props, but you will have to display the errors from the container component using reduxForm.

Pay attention that this is a total antipatern ! Global error prop is for field independent errors.

import React from 'react'
import {reduxForm} from 'redux-form'

const renderInput = ({input, meta, ...props}) => (
    <input {...input} {...props} className={meta.error ? 'error' : null} />
)

const FormWithError = ({handleSubmit, error}) => (
    <div>
        <div className='errorContainer'>
            <span {...props} className='error'>Error : {error}</span>
        </div>
        <form onSubmit={handleSubmit}>
            <Field name='myInput' component={renderInput} />
        </form>
    </div>
)

const validate = (values, props) => {
    const errors = {}
    /* calculate errors here by appending theim to errors object */
    if(Object.keys(errors) > 0) {
        //You can concatenate each error in a string
        for(key in errors) errors._error += key + ': ' + errors[key]
        //or juste put the errors object in the global error property
        errors._error = {...errors}
    }
    return errors
}

export default reduxForm({form: 'myForm', validate})(FormWithError)

Solution 3: Get errors from the store

You always can get errors from the store by applying your validate function on the value presents in the store. It can be not performant for heavy validation because it run through validation at each render, so it runs twice when a value change and one for nothing if some other props changes. It can also be dificult to do with async validation.

import React from 'react'
import {reduxForm, formValueSelector} from 'redux-form'
import {connect} from 'redux'

const renderErrors = errors => {
    const errorNodes = []
    for(key in errors) errorNodes.push(<span className='error'>{key}: {errors[key]}</span>)
    return errorNodes
}

const renderInput = ({input, meta, ...props}) => (
    <input {...input} {...props} className={meta.error ? 'error' : null} />
)

let FormWithError = ({handleSubmit, values, ...otherProps}) => (
    <div>
        <div className='errorContainer'>
            {renderErrors(validate(values, otherProps))}
        </div>
        <form onSubmit={handleSubmit}>
            <Field name='myInput1' component={renderInput} />
            <Field name='myInput2' component={renderInput} />
        </form>
    </div>
)

const validate = (values, props) => {
    const errors = {}
    /* calculate errors here by appending theim to errors object */
    return errors
}

FormWithError = reduxForm({form: 'myForm', validate})(FormWithError)
FormWithError = connect(
    state => formValueSelector('myForm')(state, 'myInput1', 'myInput2')
)(FormWithError)

A last solution can be to store the errors in the store by implementing the componentWillReceiveProps and dispatching an action to update a list of error in the store but i don't think it's really a good idea. It's better to keep simple stateless component to render a Field component.

EDIT 26/12/2018 :

This last "solution" wasn't a good one at the time I've posted it. But since componentWillReceiveProps is deprecated in React, it's not a solution at all. Please don't do this in you application. I don't delete this for history in case this answer was linked somewhere.

like image 186
Emrys Myrooin Avatar answered Oct 05 '22 06:10

Emrys Myrooin


You can use the state selectors provided by redux-form.

In particular, getFormSubmitErrors will give you the submit validation errors:

import { getFormSubmitErrors } from 'redux-form';

// ...

const MyFormWithErrors = connect(state => ({
  submitErrors: getFormSubmitErrors('my-form')(state)
}))(MyForm);

The original, unconnected MyForm component might look like this:

const MyForm = reduxForm({
  form: 'my-form',
})(ManageUserProfile);

If you want to display the synchronous validation errors, you can use the getFormSyncErrors selector instead.

like image 36
nicoqh Avatar answered Oct 05 '22 05:10

nicoqh