Fields are not editable using React Hook Form

Viewed 2425

I have this UserEditScreen.js file where in I am pulling up all the data from mongodb via React-Redux.

I need to incorporate some validation so I used React Hook Form to do this. So on my code:

import React, { useState, useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { useDispatch, useSelector } from 'react-redux'
import  Message from '../components/Message'
import  Loader from '../components/Loader'
import { getUserDetails, updateUser } from '../actions/userActions'
import { USER_UPDATE_RESET } from '../constants/userConstants'

const UserEditScreen = ({ match, history }) => {

    const { watch, register, errors, handleSubmit } = useForm()
    console.log('role')

    const userId = match.params.id

    // const [name, setName] = useState('')
    // const [email, setEmail] = useState('')
    // const [phone, setPhone] = useState('')
    // const [role, setRole] = useState('')

    const dispatch = useDispatch()

    const userDetails = useSelector(state => state.userDetails)
    const { loading, error, user } = userDetails 

    const userUpdate = useSelector(state => state.userUpdate)
    const { loading:loadingUpdate, error:errorUpdate, success:successUpdate } = userUpdate 

     useEffect(() => {

       if(successUpdate){
           dispatch({ type: USER_UPDATE_RESET })
           history.push('/admin/userslist')
       } else {
        if(!user.name || user._id !== userId){
            dispatch(getUserDetails(userId))
        }
        // else {
        //      setName(user.name)
        //      setEmail(user.email)
        //      setPhone(user.phone)
        //      setRole(user.role)
        //  }
       }
     }, [dispatch, history, userId, user, successUpdate])

    const submitHandler = (data, e) => {
        e.preventDefault()
        const {name, email, phone, role } = data
        dispatch(updateUser({ _id: userId, name, email, phone, role  }))
    }

    return ( 
        <>
            <h1>Edit User</h1>
            { loadingUpdate && <Loader /> }
    { errorUpdate && <Message variant='danger'>{errorUpdate}</Message>}
    { loading ? <Loader /> : error ? <Message variant='danger'>{error}</Message> : (
            <form onSubmit={submitHandler}> 
            <div className="form-group"> 
                <label for="name">Name</label>
                        <input
                            type="text"
                            name="name"
                            value={user.name}
                            className={`form-control ${errors.name ? 'is-invalid' : ''}`}
                            id="name"
                            ref={register({ required: true, minLength: 2, maxLength: 30 })}
                        />
                        { errors.name && errors.name.type ==='required' && <p className="text-danger">Name is required.</p> }
                        { errors.name && errors.name.type ==='minLength' && <p className="text-danger">Name is too short.</p> }
                        { errors.name && errors.name.type ==='maxLength' && <p className="text-danger">Name is exceeds maximum length.</p> }
                    </div>
                    
            <div className="form-group"> 
                <label for="email">Email address</label>
                        <input
                            type="email"
                            name="email"
                            value={user.email}
                            className={`form-control ${errors.email ? 'is-invalid' : ''}`}
                            id="email"
                            ref={register({ required: true, minLength: 8, maxLength: 30, pattern: /^\S+@\S+\.\S+$/ })}
                        />
                        { errors.email && errors.email.type ==='required' && <p className="text-danger">Email is required.</p> }
                        { errors.email && errors.email.type ==='minLength' && <p className="text-danger">Email length is too small.</p> }
                        { errors.email && errors.email.type ==='maxLength' && <p className="text-danger">Email exceeds maximum length.</p> }
                        { errors.email && errors.email.type ==='pattern' && <p className="text-danger">That is not a valid email.</p> }
            </div>
            <div className="form-group"> 
                <label for="phone">Phone</label>
                        <input
                            type="text"
                            value={user.phone}
                            className={`form-control ${errors.phone ? 'is-invalid' : ''}`}
                            id="phone"
                            ref={register({ required: true, minLength: 10, maxLength: 13, pattern: /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/})}
                        />
                        { errors.phone && errors.phone.type ==='required' && <p className="text-danger">Phone is required.</p> }
                        { errors.phone && errors.phone.type ==='minLength' && <p className="text-danger">Phone length is too small.</p> }
                        { errors.phone && errors.phone.type ==='maxLength' && <p className="text-danger">Phone exceeds maximum length.</p> }
                        { errors.phone && errors.phone.type ==='pattern' && <p className="text-danger">Phone is not a valid phone.</p> }
            </div>
            <div className="form-group"> 
                <label for="phone">User Role</label>
                        <div class="role">
                        <input 
                   type="radio" 
                   className="btn-check" 
                   name="role" 
                   id="administrator" 
                   autocomplete="off"  
                   value='administrator'
                   ref={register({ required: true })}
                   checked={user.role === 'administrator'}
                    />
                <label className="btn btn-outline-success" for="administrator">Administrator</label>
                <input 
                   type="radio" 
                   className="btn-check" 
                   name="role" 
                   id="resort-owner" 
                   autocomplete="off"  
                   value='resortOwner'
                    ref={register({ required: true })}
                    checked={user.role === 'resortOwner'}
                    />
                <label className="btn btn-outline-success" for="resort-owner">Resort Owner</label>
                <input 
                   type="radio" 
                   className="btn-check"
                   name="role" 
                   id="reviewer" 
                   autocomplete="off" 
                   value='reviewer'
                    ref={register({ required: true })}
                    checked={user.role === 'reviewer'}
                />
                <label className="btn btn-outline-success" for="reviewer">Reviewer</label>
                { errors.role && errors.role.type ==='required' && <p className="text-danger">Choose a role.</p> }
             </div>

            </div>
        
            <button type="submit" className="btn btn-primary">Update</button>
            </form>
            
    )}

        </>
    )
}

export default UserEditScreen

As you can see I commented out the useState fields and tried to pulled the data from userDetails action directly via user.name, user.email etc.. which I setup via each input fields value attribute.

However, doing this, I was not able to modify or edit any fields anymore. I need to set some validation but hoping still to be able to edit each fields and submit the form using react hook form but I am not sure how to fix this.

Any idea how can I fix this and make the form fields editable as well as be able to submit the form with the validations using react hook form? Please help.

UPDATE: I tried using defaultValue instead of value so it works on name, email, phone but now I can't select or modify the value of role the radio button one. Here's my updated code.

const UserEditScreen = ({ match, history }) => {

const { watch, register, errors, handleSubmit } = useForm()
console.log(watch('name'))
console.log(watch('email'))
console.log(watch('phone'))
console.log(watch('role'))

const userId = match.params.id

// const [name, setName] = useState('')
// const [email, setEmail] = useState('')
// const [phone, setPhone] = useState('')
// const [role, setRole] = useState('')

const dispatch = useDispatch()

const userDetails = useSelector(state => state.userDetails)
const { loading, error, user } = userDetails 

const userUpdate = useSelector(state => state.userUpdate)
const { loading:loadingUpdate, error:errorUpdate, success:successUpdate } = userUpdate 

 useEffect(() => {

   if(successUpdate){
       dispatch({ type: USER_UPDATE_RESET })
       history.push('/admin/userslist')
   } else {
    if(!user.name || user._id !== userId){
        dispatch(getUserDetails(userId))
    }

    // else {
    //      setName(user.name)
    //      setEmail(user.email)
    //      setPhone(user.phone)
    //      setRole(user.role)
    //  }
   }
 }, [dispatch, history, userId, user, successUpdate,])

const submitHandler = (data, e) => {
    e.preventDefault()
    const {name, email, phone, role } = data
    dispatch(updateUser({ _id: userId, name, email, phone, role  }))
}

return ( 
    <>
        <h1>Edit User</h1>
        { loadingUpdate && <Loader /> }
{ errorUpdate && <Message variant='danger'>{errorUpdate}</Message>}
{ loading ? <Loader /> : error ? <Message variant='danger'>{error}</Message> : (
        <form onSubmit={handleSubmit(submitHandler)}> 
        <div className="form-group"> 
            <label for="name">Name</label>
                    <input
                        type="text"
                        name="name"
                        defaultValue={user.name}
                        className={`form-control ${errors.name ? 'is-invalid' : ''}`}
                        id="name"
                        ref={register({ required: true, minLength: 2, maxLength: 30 })}
                    />
                    { errors.name && errors.name.type ==='required' && <p className="text-danger">Name is required.</p> }
                    { errors.name && errors.name.type ==='minLength' && <p className="text-danger">Name is too short.</p> }
                    { errors.name && errors.name.type ==='maxLength' && <p className="text-danger">Name is exceeds maximum length.</p> }
                </div>
                
        <div className="form-group"> 
            <label for="email">Email address</label>
                    <input
                        type="email"
                        name="email"
                        defaultValue={user.email}
                        className={`form-control ${errors.email ? 'is-invalid' : ''}`}
                        id="email"
                        ref={register({ required: true, minLength: 8, maxLength: 30, pattern: /^\S+@\S+\.\S+$/ })}
                    />
                    { errors.email && errors.email.type ==='required' && <p className="text-danger">Email is required.</p> }
                    { errors.email && errors.email.type ==='minLength' && <p className="text-danger">Email length is too small.</p> }
                    { errors.email && errors.email.type ==='maxLength' && <p className="text-danger">Email exceeds maximum length.</p> }
                    { errors.email && errors.email.type ==='pattern' && <p className="text-danger">That is not a valid email.</p> }
        </div>
        <div className="form-group"> 
            <label for="phone">Phone</label>
                    <input
                        type="text"
                        name="phone"
                        defaultValue={user.phone}
                        className={`form-control ${errors.phone ? 'is-invalid' : ''}`}
                        id="phone"
                        ref={register({ required: true, minLength: 10, maxLength: 13 })}
                    />
                    { errors.phone && errors.phone.type ==='required' && <p className="text-danger">Phone is required.</p> }
                    { errors.phone && errors.phone.type ==='minLength' && <p className="text-danger">Phone length is too small.</p> }
                    { errors.phone && errors.phone.type ==='maxLength' && <p className="text-danger">Phone exceeds maximum length.</p> }
                    { errors.phone && errors.phone.type ==='pattern' && <p className="text-danger">Phone is not a valid phone.</p> }
        </div>
        <div className="form-group"> 
            <label for="phone">User Role</label>
                    <div class="role">
                    <input 
               type="radio" 
               className="btn-check" 
               name="role" 
               id="administrator" 
               autocomplete="off"  
               value='administrator'
               ref={register({ required: true })}
               checked={user.role === 'administrator'}
                />
            <label className="btn btn-outline-success" for="administrator">Administrator</label>
            <input 
               type="radio" 
               className="btn-check" 
               name="role" 
               id="resort-owner" 
               autocomplete="off"  
               value='resortOwner'
                ref={register({ required: true })}
                checked={user.role === 'resortOwner'}
                />
            <label className="btn btn-outline-success" for="resort-owner">Resort Owner</label>
            <input 
               type="radio" 
               className="btn-check"
               name="role" 
               id="reviewer" 
               autocomplete="off" 
               value='reviewer'
                ref={register({ required: true })}
                checked={user.role === 'reviewer'}
            />
            <label className="btn btn-outline-success" for="reviewer">Reviewer</label>
            { errors.role && errors.role.type ==='required' && <p className="text-danger">Choose a role.</p> }
         </div>

        </div>
    
        <button type="submit" className="btn btn-primary">Update</button>
        </form>
        
)}

    </>
)

}

2 Answers

Don't use value attribute on inputs. React will render form controls uneditable if you don't provide a value for onChange as well. Use reset method of hook to set field values asynchronously instead:

const { watch, register, errors, handleSubmit, reset } = useForm();
...
useEffect(() => {
  reset({
    name: user.name,
    email: user.email,
    ...
  });
}, [reset, user]);

Read the React docs on controlled form components

See example on codesandbox.io

I think so you must try use onChange or other listener to inputs and set inner value from listener callback. The inner value is your state. It's normally React use for controlled inputs and setting value on onChange re-renders this component with new value and will be editable. Try ;)

Related