I'm trying to disable the submit button for my form. I've removed most of the handlers and inputs accept for the 'name' input, so it's clearer to see. The name input works fine, however when I add a validation prop to the name input, and disabled prop to the submit button, the button still stays active. What am I missing?
export class RecipeForm extends React.Component {
onChange = e => {
const { value, name } = e.target;
this.setState({
[name]: value
});
};
// **** other function handlers here *****//
required = value => {
if (!value || value === '') {
return 'This field is required';
}
return undefined;
}
render() {
const { name } = this.state;
const { submitSucceeded, handleSubmit, valid } = this.props;
if (submitSucceeded) { // submitSucceeded is a prop of redux form, boolean: true, and succeed to submit
alert("You have successfully added a new recipe")
return <Redirect to='/your-menu' />
}
let newCategory;
if (otherCheckbox) {
newCategory = <NewCategory addNewCategory={this.addNewCategory} />;
}
return (
<div className="form">
<Alerts />
<form onSubmit={handleSubmit(recipe => this.onSubmit(recipe))}>
<h2>Add a new favorite recipe!</h2>
<label htmlFor="name"> Recipe Name </label>
<input
name="name"
id="name"
type="text"
label="Recipe Name"
value={name}
validate={[this.required]}
onChange={this.onChange}
/>
// **** other inputs here ***** ///
<button type="submit"
disabled={!this.props.valid}
>Submit</button>
</form>
<br></br>
</div>
);
}
}
const mapStateToProps = state => ({
menuItems: state.menu.menuItems,
categoryList: state.users.categoryList,
userId: state.auth.id,
authToken: state.auth.authToken
});
RecipeForm = connect(mapStateToProps)(RecipeForm);
export default reduxForm({
form: "recipe",
onSubmitFail: (errors, dispatch) =>
dispatch(focus("recipe", Object.keys(errors)[0]))
})(RecipeForm);
Related
I have a React form where I can't control the value of the checkbox input with the useState hook. I don't have this problem with other inputs.
I can't pass the checkbox input value to the AuthData object. When you click the "Sign in" button, the console should display an AuthData object with the fields { login: '', password: '', isRemember: '' }
import React from 'react'
import { useState } from 'react'
export const AuthForm = ({ handlers }) => {
const [authData, setAuthData] = useState({ login: '', password: '', isRemember: '' })
const changeValue = (event) => {
const { id, value } = event.target
setAuthData((prevAuthData) => ({ ...prevAuthData, [id]: value }))
}
const signIn = () => {
console.log(authData)
}
return (
<form onSubmit={(e) => e.preventDefault()}>
<input
type="text"
id="login"
placeholder="Login/E-mail/Phone"
value={authData.login}
onChange={changeValue}
/>
<input
type="password"
id="password"
placeholder="Password"
value={authData.password}
onChange={changeValue}
/>
<input
type="checkbox"
id="isRemember"
value={authData.isRemember}
onChange={changeValue}
/>
<button onClick={signIn}>Sign in</button>
</form>
)
}
When you change inputs values, their values must be passed to the authValue object.
With "login" and "password" inputs their values go into the authValue object, but with "isRemember" input this does not work. The value of checkbox inputs somehow does not get into the authValue object.
you can check the input type and get the checked value for checkbox from the event object as below
const changeValue = (event) => {
let { id, value, type, checked="" } = event.target;
if (type === "checkbox") {
value = checked;
}
setAuthData((prevAuthData) => ({ ...prevAuthData, [id]: value }));
};
You have to use the checked attribute on the checkbox input.
The value attribute is used, but you’ll have to modify it to ensure it sends true or false to the state object
I've added a snippet in response to your comment.
const {useState} = React
const AuthForm = ({ handlers }) => {
const [authData, setAuthData] = useState({ login: '', password: '', isRemember: false })
const changeValue = (event) => {
const { id, value } = event.target
setAuthData((prevAuthData) => ({ ...prevAuthData, [id]: value }))
}
const changeCheckbox = () => {
setAuthData((prevAuthData) => ({ ...prevAuthData, isRemember: !prevAuthData.isRemember }))
}
const signIn = () => {
console.log(authData)
}
console.log(authData);
return (
<form onSubmit={(e) => e.preventDefault()}>
<input
type="text"
id="login"
placeholder="Login/E-mail/Phone"
value={authData.login}
onChange={changeValue}
/>
<input
type="password"
id="password"
placeholder="Password"
value={authData.password}
onChange={changeValue}
/>
<input
type="checkbox"
id="isRemember"
checked={authData.isRemember}
onChange={changeCheckbox}
/>
<button onClick={signIn}>Sign in</button>
</form>
)
}
// Render it
ReactDOM.createRoot(
document.getElementById("root")
).render(
<AuthForm />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
I was working on a react app which needs a registration system. I have used the same process in every required function, and it has not worked in any of them. I want to debug this before I move on. This is the form for sending password to user's email if the user forgets their password.
const initialState = {
email: ''
};
class EmailPassword extends Component {
constructor(props) {
super(props);
this.state = {
...initialState
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const { name, value } = e.target;
this.setState({
[name]: value
})
}
handleSubmit = async (e) => {
e.preventDefault();
}
render() {
const { email } = this.state;
const configAuthWrapper = {
headline: 'Email Password'
};
return (
<AuthWrapper {...configAuthWrapper}>
<div className='formWrap'>
<form onSubmit={this.handleSubmit}>
<FormInput
type = "email"
name = "email"
value = {email}
placeholder = 'Email'
onChange = {this.handleChange}
/>
</form>
<Button type='submit'>
Send email
</Button>
</div>
</AuthWrapper>
);
}
}
This is what the output looks like, with no apparent validation of email when a wrong email is submitted:
What it's supposed to show:
My Button was not wrapped inside the form.
The solution I used:
<AuthWrapper {...configAuthWrapper}>
<div className='formWrap'>
<form onSubmit={this.handleSubmit}>
<FormInput
type = "email"
name = "email"
value = {email}
placeholder = 'Email'
handleChange = {this.handleChange}
/>
<Button type="submit">
Send email
</Button>
</form> //form must wrap the button
</div>
</AuthWrapper>
I am getting state values while clicking submit button but I am unable to do the validation for my login form and how to display the error messages below the input field when I enter my input wrong or empty. please give me a solution to this.Thanks in advance.
const Login = () => {
const [state, setState] = useState({
email: "",
password: ""
});
const handleChange = (e) => {
const {id, value} = e.target
setState(prevState => ({
...prevState,
[id]: value
}))
}
const handleSubmitClick = (e) => {
e.preventDefault();
console.log("Authenticated",state);
}
return(
<>
<div className="container">
<div className="title">
<form onSubmit={handleSubmitClick}>
<div className="form-group">
<input
type="email"
className="email"
placeholder="Email"
value={state.email}
onChange={handleChange}/>
</div>
<div className="form-group">
<input
type="password"
className="password"
placeholder="Password"
value={state.password}
onChange={handleChange}/>
</div>
<button type="submit" className="button">Enter</button>
</form>
</div>
</div>
</>
)
}
export default Login;
If you want to perform client-side validation, you can create hook like this:
const useEmailValidation = (email) => {
const isEmailValid = /#/.test(email); // use any validator you want
return isEmailValid;
};
And then you can use this hook in your form component:
...
const isEmailValid = useEmailValidation(state.email);
const isPasswordValid = usePasswordValidation(state.password);
const isFormValid = isEmailValid && isPasswordValid;
return (
...
<input
className={classNames({ 'invalid': !isEmailValid })}
type="email"
value={state.email}
onChange={handleChange}
/>
{!isEmailValid && 'Some error message'}
<button type="submit" disabled={!isFormValid} className="button">Enter</button>
...
);
...
Your validator hook can return validation message instead of boolean, like:
const useEmailValidation = (email) => {
if (!email || email.length === 0) {
return 'Email cannot be empty';
}
const isEmailValid = /#/.test(email); // use any validator you want
if (!isEmailValid) {
return 'Invalid email provided';
}
return null;
};
Also it is a good practice to show validation message only after field was focused before and after user tried to submit the form.
Formik is a great plugin that will help you perform form validation. The examples are also quite clear.
Or you could do something like this:
const Login = () => {
const [error, setError] = useState(null);
const [state, setState] = useState({
email: '',
password: '',
});
const validateEmail = (email) => {
const re =
/^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
};
const handleChange = (e) => {
const { id, value } = e.target;
setState((prevState) => ({
...prevState,
[id]: value,
}));
};
const handleSubmitClick = (e) => {
e.preventDefault();
if (!validateEmail(state.email)) {
setError('Invalid Email');
}
if (state.password.length < 8) {
setError('Password must be at least 8 chars long');
}
if (!error) {
// No errors.
}
};
return (
<>
<div className='container'>
<div className='title'>
{error && <div style={{ color: 'red' }}>{error}</div>}
<form onSubmit={handleSubmitClick}>
<div className='form-group'>
<input
type='email'
className='email'
placeholder='Email'
value={state.email}
onChange={handleChange}
/>
</div>
<div className='form-group'>
<input
type='password'
className='password'
placeholder='Password'
value={state.password}
onChange={handleChange}
/>
</div>
<button type='submit' className='button'>
Enter
</button>
</form>
</div>
</div>
</>
);
};
export default Login;
For an empty validation you can check it preventing the submit if the field is empty, like
const handleSubmitClick = (e) => {
e.preventDefault();
if(email.trim() === '' || password.trim() === ''){
//Add a h1 or section with the error message
}else{
console.log("Authenticated",state);
}
}
As long as the email field type is equal to email, which is your case, the browser should give an alert if the string is not an email. ("user#example.com")
I am having some issues with setting the inital form field values using redux-form.
Here is the code I tried
import { Field, FieldArray, reduxForm, getFormValues, change } from 'redux-form'
const renderField = ({
input,
label,
type,
meta: { asyncValidating, touched, error }
}) => (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} type={type} placeholder={label}/>
{touched && error && <span>{error}</span>}
</div>
</div>
)
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
firstName: null,
lastName: null,
}
}
componentDidMount() {
this.props.fetchProfile();
}
async handleChange(e) {
await this.setState({ 'initialValues': { [e.target.name] : e.target.value }});
await this.setState({ [e.target.name] : e.target.value });
}
onSubmit = (e) => {
this.props.saveProfile({
firstName: this.state.auth.firstName,
lastName: this.state.auth.lastName,
});
}
componentWillReceiveProps(nextProps) {
this.setState({
firstName : nextProps.initialValues.firstName,
lastName : nextProps.initialValues.LastName,
});
this.setState({ 'initialValues': {
firstName : nextProps.initialValues.firstName,
lastName : nextProps.initialValues.LastName,
}});
}
render() {
return (
<>
<form onSubmit={handleSubmit(this.onSubmit)}>
<div>
<Field
name="firstName"
type="text"
component={renderField}
label="firstName"
onChange={this.handleChange.bind(this)}
/>
</div>
<div>
<Field
name="lastName"
type="text"
component={renderField}
label="lastName"
onChange={this.handleChange.bind(this)}
/>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Update Info
</button>
</div>
</form>
);
}
}
Profile = reduxForm({
form: 'Profile' ,
// fields,
validate,
asyncValidate,
enableReinitialize: true,
})(Profile);
function mapStateToProps(state, props){
let firstName = '';
let lastName = '';
return {
userData: state.auth.userData,
initialValues:{
firstName: state.auth.firstName,
lastName: state.auth.lastName,
}
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchProfile: () => dispatch(auth.actions.fetchProfile()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
But it is not setting value in field when load. field is just empty
I guess Redux Form works little different.
You don't need to set explicit onChange handler or declare any state to hold form fields data in Redux Form. Update your code similar to below
import { Field, FieldArray, reduxForm, getFormValues, change } from 'redux-form'
const renderField = ({
input,
label,
type,
meta: { asyncValidating, touched, error }
}) => (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} type={type} placeholder={label}/>
{touched && error && <span>{error}</span>}
</div>
</div>
)
class Profile extends Component {
// No need constructor/to explicitly declare the state
componentDidMount() {
this.props.fetchProfile();
}
render() {
const { handleSubmit, pristine, submitting} = props; // You can have custom handleSubmit too
return (
<>
<form onSubmit={handleSubmit}>
<div>
<Field
name="firstName"
type="text"
component={renderField}
label="firstName"
/>
</div>
<div>
<Field
name="lastName"
type="text"
component={renderField}
label="lastName"
/>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Update Info
</button>
</div>
</form>
);
}
}
Profile = reduxForm({
form: 'Profile' ,
// fields,
validate,
asyncValidate,
enableReinitialize: true,
})(Profile);
function mapStateToProps(state, props) {
return {
userData: state.auth.userData,
initialValues:{ // These are the initial values. You can give any name here for testing to verify the ReduxForm working
firstName: state.auth.firstName,
lastName: state.auth.lastName,
}
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchProfile: () => dispatch(auth.actions.fetchProfile()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
The example in question and the answer would definitely help you to understand things better.
I'm trying to fill the profile form with data from API. Unfortunately redux-form doesn't want to cooperate with me in this case. For some reason fields stays empty whatever I do.
Setting the fixed values instead of values passed from reducer work well for some reason.
Maybe this is because I'm using redux-promise for API calls inside the action creators? How can I live with it and get rid of this. Here is my form component.
import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import { fetchRoleList, fetchUserData } from '../actions';
class UserEdit extends Component {
componentWillMount() {
this.props.fetchRoleList();
this.props.fetchUserData();
}
handleEditProfileFormSubmit(formProps) {
console.log(formProps);
}
getRoleOptions(selected_id) {
if (!this.props.profile) {
return <option>No data</option>;
}
return this.props.profile.roles.map(role => {
return <option key={role.role_id} value={role.role_id}>{role.name}</option>;
});
}
renderField(props) {
const { input, placeholder, label, value, type, meta: { touched, error } } = props;
return (
<fieldset className={`form-group ${ (touched && error) ? 'has-error' : '' }`}>
<label>{label}</label>
<input className="form-control" {...input} type={type} placeholder={placeholder} />
{touched && error && <div className="error">{error}</div>}
</fieldset>
);
}
renderSelect({ input, placeholder, options, label, type, meta: { touched, error } }) {
return (
<fieldset className={`form-group ${ (touched && error) ? 'has-error' : '' }`}>
<label>{label}</label>
<select className="form-control" {...input}>
{options}
</select>
{touched && error && <div className="error">{error}</div>}
</fieldset>
);
}
render() {
const { handleSubmit } = this.props;
const user = this.props.profile.user;
return (
<div> {user ? user.email : ''}
<form onSubmit={handleSubmit(this.handleEditProfileFormSubmit.bind(this))}>
<Field name="email" label="Email:" component={this.renderField} type="text" placeholder="email#gmail.com" className="form-control"/>
<Field name="name" label="Name:" component={this.renderField} type="text" placeholder="John Doe" className="form-control"/>
<Field name="role" label="Role:" component={this.renderSelect} type="select" className="form-control" options={this.getRoleOptions()}/>
<button action="submit" className="btn btn-primary">Edit user</button>
<Field name="password" label="Password:" component={this.renderField} type="password" className="form-control"/>
<Field name="passwordConfirm" label="Confirm Password:" component={this.renderField} type="password" className="form-control"/>
{ this.props.errorMessage
&& <div className="alert alert-danger">
<strong>Oops!</strong> {this.props.errorMessage}
</div> }
<button action="submit" className="btn btn-primary">Sign up!</button>
</form>
</div>
);
}
}
let InitializeFromStateForm = reduxForm({
form: 'initializeFromState'
})(UserEdit);
InitializeFromStateForm = connect(
state => ({
profile: state.profile,
initialValues: state.profile.user
}),
{ fetchRoleList, fetchUserData }
)(InitializeFromStateForm);
export default InitializeFromStateForm;
I do believe action creator will be useful as well:
export function fetchUserData(user_id) {
user_id = user_id ? user_id : '';
const authorization = localStorage.getItem('token');
const request = axios.get(`${ROOT_URL}/user/${user_id}`, {
headers: { authorization }
});
return {
type: FETCH_USER,
payload: request
};
}
You need to add enableReinitialize: true as below.
let InitializeFromStateForm = reduxForm({
form: 'initializeFromState',
enableReinitialize : true // this is needed!!
})(UserEdit)
If your initialValues prop gets updated, your form will update too.
To set the initialValues it is important to apply the reduxForm() decorator before the connect() decorator from redux. The fields will not populate from the store state if the order of the decorators is inverted.
const FormDecoratedComponent = reduxForm(...)(Component)
const ConnectedAndFormDecoratedComponent = connect(...)(FormDecoratedComponent)
If, in addition to setting the values for the first time, you need to re-populate the form every time the state changes then set enableReinitialize: true
Find a simple example in this answer.
Read the official documentation and full example.
Read about this issue here.
So, you're trying:
Load API data into the form
Update the form just on load (aka. initialValues)
Whilst #FurkanO might work, I think the best approach is to load the form when you got all async data, you can do that by creating a parent component / container:
UserEditLoader.jsx
componentDidMount() {
// I think this one fits best for your case, otherwise just switch it to
// componentDidUpdate
apiCalls();
}
/* api methods here */
render() {
const { profile } = this.props;
return (
{profile && <UserEdit profile={profile} />}
);
}
Basically what you should be doing in the UserEditLoader is to execute the API functions and update the state (or props if redux connected). Whenever the profile variable isn't empty (meaning you got the data you were expecting) then mount UserEdit with profile as prop.
initialize() is a prop provided by reduxForm, that can be used to fill up the form values.
change() is another prop provided by reduxFrom to change a field value.
import * as React from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
const submit = values => {
// print the form values to the console
console.log(values)
}
interface Props {
history?: any;
location?: any;
session?: any;
handleSubmit?: Function;
initialize?: Function;
change?: Function;
}
class ContactForm extends React.Component<Props, any> {
constructor(props, state) {
super(props, state);
this.state = {
value: ''
};
}
componentDidMount() {
const { initialize, session, location } = this.props;
console.log(location.pathname);
if (session && session.user) {
const values = {
firstName: session.user.name,
lastName: session.user.lastName,
email: session.user.email
};
initialize(values);
}
}
componentWillReceiveProps(nextProps) {
const { initialize, session } = this.props;
if (nextProps.session !== session) {
if (nextProps.session && nextProps.session.user) {
const values = {
firstName: nextProps.session.user.name,
lastName: nextProps.session.user.lastName,
email: nextProps.session.user.email
};
initialize(values);
} else {
const values = {
firstName: null,
lastName: null,
email: null
};
initialize(values);
}
}
}
render() {
const { handleSubmit, change } = this.props;
return (
<React.Fragment>
<form onSubmit={handleSubmit(submit)}>
<div>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" component="input" type="text" />
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" component="input" type="text" />
</div>
<div>
<label htmlFor="email">Email</label>
<Field name="email" component="input" type="email" />
</div>
<button type="submit">Submit</button>
</form>
<input type="text" value={this.state.value}
onChange={(e) => {
this.setState({ value: e.target.value });
change('firstName', e.target.value);
}}
/>
</React.Fragment>
);
}
}
export default connect((state) => {
return {
session: state.session
}
},
{}
)(withRouter((reduxForm({
form: 'contact'
})(ContactForm))));
If the enableReinitialize : true trick does not work, you can update each field when the initialValues prop changes.
componentWillReceiveProps(nextProps) {
const { change, initialValues } = this.props
const values = nextProps.initialValues;
if(initialValues !== values){
for (var key in values) {
if (values.hasOwnProperty(key)) {
change(key,values[key]);
}
}
}
}
I have never worked with FieldsArray but I assume this would not work here.
For a stateless functional component, you can do it like this:
componentWillMount() {
this.props.initialize({ discountCodes: ["ABC200", "XYZ500"] });
}
For a class, you can do it like this:
const mapStateToProps = state => (
{
initialValues: {
discountCodes: ["ABC200", "XYZ500"]
}
);