Search code examples
javascriptreactjslodash

How to disable login button on a dynamically rendered form


I am working on a login form where each input is created dynamically as a field. This is my Login.js file:

   import _ from 'lodash';
import React, { Component } from 'react';
import {reduxForm, Field } from 'redux-form';
import{ Link } from 'react-router-dom';
import FIELDS from './loginFields';
import LoginField from './LoginField'
import { connect } from 'react-redux';
import * as actions from '../../actions'

class LoginForm extends Component {
    constructor(){
        super();
            this.state={
               username: '',
               password: ''
            };
        };

    handleChange = (e)=>{
            this.setState({username: e.target.value, password: e.target.value});
             };
    
    renderFields(){
        return _.map(FIELDS, ({ label, name, type })=> {
            return <Field onChange={this.handleChange} className='purple-text' key={name} component={ LoginField } type={type} label={label} name={name} />
        });
    };

    render(){
        const { username, password } = this.state;
        const isEnabled = username.length > 0 && password.lenth>7;
        return (
            <div className='valign-wrapper row login-box' style={{marginTop:'100px'}}>
                <div className='col card hoverable s10 pull-s1 m6 pull-m3 l4 pull-l4'>
                    <form method='POST' action='/api/login'>
                         <div className = 'card-content'>
                             <span className='card-title purple-text' style={{textAlign:'center'}}>Login<a href='/register'> Not a member? sign up!</a></span>
                             <div className='center-align row'>
                                <li key='google' style={{marginLeft: '30px'}} className='col m6 center-align white-text darken-3'><a className='white-text' href='/auth/google'><img alt="" src="https://img.icons8.com/cute-clipart/64/000000/google-logo.png"/></a></li>
                                <li key='facebook' className='col  center-align white-text darken-3'><a className='white-text' href='/auth/facebook'><img alt = "" src="https://img.icons8.com/cute-clipart/64/000000/facebook-new.png"/></a></li>
                             </div>
                                <div className='row input-field col s12'>
                                 {this.renderFields()}
                                <Link to='/' className='purple btn-flat left white-text'>Back</Link>
                                <button disabled={!isEnabled} type='submit' className='purple btn-flat right white-text'>Login
                                 <i className='material-icons right'>done</i>
                                </button>
                                
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        );
    };
};

function validate(values){
    const errors = {};

    _.each(FIELDS, ({name})=>{
        if(!values[name]){
            errors[name] = 'You must enter a value!'
        } 
    });
    return errors;
    
};

const form =  reduxForm({
    validate,
    form: 'LoginForm'
});


export default connect(null, actions)(form(LoginForm));

Here is loginFields.js

export default

[
    { label: 'Username', name: 'username', type: 'text'},
    { label: 'Password', name: 'password', type: 'password'}
];

and here is LoginField.js

import React from 'react';

export default ({ input, label, type, meta })=>{

    return(
        <div>
            <label className='purple-text'>{label}</label>
            <input {...input} type={type} style= {{marginBottom: '5px'}}/>
            <div className = "red-text" style={{ marginBottom: '20px'}}>
                {meta.touched && meta.error}
            </div>
        </div>
            
    );
};

I am having trouble properly setting onChange and my constructor to disable the login button until all fields are filled. I have been able to disable the button until a single input has started to be filled in, not disabled at all, and not enabled at all. but have not been able to achieve the desired outcome.

I have tried using lodash to map over each field grabbing values by the input name property, and moving functions around.

Any help would be greatly appreciated, if i can provide any more information for this question please let me know.


Solution

  • The initial problem I see is the onChange function will update state for both password and username whenever either of them is changed. The function takes the event and does not distinguish as to which input is the target. You can pass an additional parameter from the Field that includes the field name, or you can check the target's id or something so you know which input's state should be updated.

    In LoginForm

     handleChange = (e, name)=>{
        this.setState({[name]: e.target.value});
                 };
    

    You also need to pass the onChange callback down to the actual input in LoginField.js

    import React from 'react';
    
    export default ({ name, label, type, meta, onChange, ...props })=>{
    
        return(
            <div>
                <label className='purple-text'>{label}</label>
                <input onChange={(e) => onChange(e, name)} {...props} type={type} style= {{marginBottom: '5px'}}/>
                <div className = "red-text" style={{ marginBottom: '20px'}}>
                    {meta.touched && meta.error}
                </div>
            </div>
                
        );
    };
    

    Here's a codeSandbox.

    Edit jolly-yalow-z77es