Inputs not updating state in functional react component - reactjs

I looked around and I see similar questions, but whenever I follow the answers I can't seem to get this to work in the way that I have it written. I am starting off all four states as blank inside of an array, but I want to update the states as the user types for each input field. Why is setChanging not working to update the state for the particular name of the input field? Console logs both the x and y values as I type into each input. I must be doing something simple wrong, but I can't figure out what it is.
const ContactForm = () => {
const initialValues = {
recipientName: "",
recipientEmail: "",
fromName: "",
fromEmail: "",
};
const [changing, setChanging] = useState(initialValues);
const handleInputChange = (e) => {
let x = e.target.name;
let y = e.target.value;
console.log(x);
console.log(y);
setChanging({...changing, [e.target.name]: e.target.value});
console.log(initialValues);
}
return (
<Form>
<Form.Group className="mb-3">
<Row>
<Col>
<Form.Control
type="text"
required
name="recipientName"
placeholder="Recipient Name*"
id="form.recipientName"
onChange={handleInputChange}
/>
</Col>
<Col>
<Form.Control
type="email"
required
name="recipientEmail"
placeholder="Recipient Email*"
id="form.recipientEmail"
onChange={handleInputChange}
/>
</Col>
</Row>
</Form.Group>
<Form.Group className="mb-3">
<Row>
<Col>
<Form.Control
type="text"
required
name="fromName"
placeholder="From Name*"
aria-invalid="form.fromName"
onChange={handleInputChange}
/>
</Col>
<Col>
<Form.Control
type="email"
required
name="fromEmail"
placeholder="From Email*"
id="form.fromEmail"
onChange={handleInputChange}
/>
</Col>
</Row>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
);
}
export default ContactForm

You're logging the initialValues:
console.log(initialValues);
So you're always seeing the value of initialValues, which never changes. Nowhere are you observing state.
You can respond to state updates and log the updated state with useEffect for example:
useEffect(() => console.log(changing), [changing]);
This would log the value of changing any time that value changes.
You'd also observe updates to state in the UI if/when your UI uses state to render output. (Currently it does not.)

There are some things I suggest you to change:
<Form.Control
type="text"
required
name="fromName"
placeholder="From Name*"
aria-invalid="form.fromName"
onChange={handleInputChange}
/>
I'm not sure if those components belong to a framework like MaterialUI but would be better to have an attribute called value where you pass the state to handle a controlled component instead of an uncontrolled component:
<Form.Control
type="text"
required
name="fromName"
placeholder="From Name*"
aria-invalid="form.fromName"
onChange={handleInputChange}
value={changing.fromName} // Add this attribute
/>
Also, would be better if your initialState is outside of the function.
console.log(initialValues);
You should print the state instead of the initialValues, what you are updating is the state, not the initialValues.
setChanging({...changing, [e.target.name]: e.target.value});

this is because you didn't specify value attribute on your inputs.
in addition to this to see the changes on user type you must console.log the state (which is change here) not the initialValues.
example:
<Form.Control
type="text"
required
name="recipientName"
placeholder="Recipient Name*"
id="form.recipientName"
value={changing.fromName}
onChange={handleInputChange}
/>

Related

Check if React Bootstrap Form is filled in or not to conditionally disable submit button

We have the following contact form in React using https://react-bootstrap.github.io/forms/overview/
let contactForm =
(<Form ref={formRef} onSubmit={sendEmail} className='toggle-contact-form'>
<div className='toggle-contact-form__header'>
<p className='p1'>Please Reach out!</p>
<p className='p2'>Use our contact form to reach out with any questions, concerns or issues with the website.</p>
</div>
<Form.Row style={{ paddingTop: 20 }}>
<Form.Group as={Col} controlId='name'>
<Form.Label>Name</Form.Label>
<Form.Control className='cbb-home-input' placeholder='required' />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId='email'>
<Form.Label>Email Address</Form.Label>
<Form.Control className='cbb-home-input' type='email' placeholder='required' />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId='phone'>
<Form.Label>Phone Number</Form.Label>
<Form.Control className='cbb-home-input' placeholder='optional' />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId='message'>
<Form.Label>Message</Form.Label>
<Form.Control className='cbb-home-input' as='textarea' rows='2' placeholder='required' />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId='button'>
<Button variant='primary' type='submit' disabled={true}>
{isSubmitting ? 'Sending Email...' : 'Submit'}
</Button>
</Form.Group>
</Form.Row>
</Form>);
Currently the button disabled={true}, we'd like to make this conditional on the Form.Control elements for name, message both being not empty, and email being a valid email address. Currently we have no form validation. Is it possible to validate this form as such?
The Bootstrap docs suggest using a library to make this process easier:
It's often beneficial (especially in React) to handle form validation via a library like Formik, or react-formal. In those cases, isValid and isInvalid props can be added to form controls to manually apply validation styles.
But here's how you can do it without a library:
Since we need access to the values of the input fields, we'll need to use controlled components to hold the form data. First we will set up some useState variables to hold the data:
const [name, setName] = useState("");
const [message, setMessage] = useState("");
const [email, setEmail] = useState("");
Then we need to use those state variables to handle the data in form fields by setting the value and onChange props:
...
<Form.Control
value={name}
onChange={(e) => {
setName(e.target.value);
}}
className="cbb-home-input"
placeholder="required"
/>
...
<Form.Control
value={email}
onChange={(e) => {
setEmail(e.target.value);
}}
className="cbb-home-input"
type="email"
placeholder="required"
/>
...
<Form.Control
value={message}
onChange={(e) => {
setMessage(e.target.value);
}}
className="cbb-home-input"
as="textarea"
rows="2"
placeholder="required"
/>
...
Now that we have access to the form field data, we can create a variable to keep track of whether the user input is valid:
const isValid = checkValidity(name, message, email);
The checkValidity function can check if name, message, and email meet the requirements we want them too:
const checkEmail = (email) => {
return /^\w+#[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/.test(email);
};
const checkValidity = (name, message, email) => {
return !!name && !!message && checkEmail(email);
};
At this point, the isValid variable will always be updated with whether or not the current user input in the form is valid. Specifically, we are making sure name and message are not empty, and that email passes a simple regex validity check.
Finally, we disable the submit button whenever isValid is false using the disabled prop:
<Button variant="primary" type="submit" disabled={!isValid}>
{isSubmitting ? "Sending Email..." : "Submit"}
</Button>
Here's a full working example on CodeSandbox:

React FormControl onChange not being triggered

I am trying to get a simple form working in React but I can't seem to get it working. I am also using react-bootstrap for my GUI components.
I have a component called Inventory that holds the form:
class Inventory extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
'upc' : ''
}
}
handleChange(event) { this.setState({'upc' : event.target.value }); }
handleSubmit(event) {
// Do some stuff
}
render() {
return (
<Container fluid>
<h1>Inventory Page</h1>
<Row>
<Col>
<Form onSubmit={this.handleSubmit}>
<Form.Group className="mb3" controlId="invUpcForm">
<Form.Label>UPC</Form.Label>
<Form.Control type="text" placeholder="123456789012" />
<Form.Text value={this.state.upc} onChange={this.handleChange} className="text-muted">
Ensure the field is focused, and scan the barcode.
</Form.Text>
</Form.Group>
<Button variant="primary" type="submit">Submit</Button>
</Form>
</Col>
</Row>
</Container>
);
}
}
However, whenever I enter any text into the Input field, handleChange never gets called despite the value of the field changing. If I submit the field, checking the value of upc in state shows it is empty ''.
What am I doing wrong?
According to this example from the react-bootstrap docs, you should be attaching your value and onChange props to the <Form.Control /> component, not <Form.Text />. Form text is for help text, while the form controls handle the actual input elements.
You gotta change like this.
render() {
return (
<Container fluid>
<h1>Inventory Page</h1>
<Row>
<Col>
<Form onSubmit={this.handleSubmit}>
<Form.Group className="mb3" controlId="invUpcForm">
<Form.Label>UPC</Form.Label>
<Form.Control
type="text"
placeholder="123456789012"
value={this.state.upc}
onChange={this.handleChange}
/>
<Form.Text className="text-muted">
Ensure the field is focused, and scan the barcode.
</Form.Text>
</Form.Group>
<Button variant="primary" type="submit">Submit</Button>
</Form>
</Col>
</Row>
</Container>
);
}
It is because Form.Control is a real input component, and Form.Text component is just a simple div/span to render text. You can check it by inspecting element in chrome browser.

Antd form: passing dynamic id alongside form-values

im working on a commenting system. Every ID has some comments. If I want to make a new comment, the ID has to be passed alongside the form-values.
ID is being rendered dynamically:
{data && data.formations.map(formation =>
<Row>
<Col span={2}>
<p>ID: {formation.id}</p>
</Col>
...
Form has a Textarea, and the value is being passed automatically due the name prop in Form.Item.
<Form onFinish={onFinish}>
<Form.Item name="comment">
<TextArea rows={4} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Add comment</Button>
</Form.Item>
</Form>
Idealy, I want the ID passed alongside the values in the following function OnFinish:
const onFinish = (values) *id somehwere here* => {
console.log(values)
}
How to achieve this? Thanks in advance!
You could solve this by adding an hidden field which holds the ID :
<Form onFinish={onFinish}>
<Form.Item name="comment">
<TextArea rows={4} />
</Form.Item>
<Form.Item name="id">
<Input type="hidden" value={formation.id} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Add comment</Button>
</Form.Item>
</Form>
Then in the onFinish callback you should get a new property id in the values object.
This is the common way to pass known/readonly values alongside form values (in the non SPA / synchronous web world, eg PHP).

What is the best way to handle form inputs state in a controlled REACT component?

I have a basic form in React component.
For example :
export default class SimpleForm extends Component {
_isMounted = false;
state = {
firstName:'',
lastName : ''
};
changeState(data){
this.setState(data);
}
handleInput(event){
this.changeState({
[event.target.name]:event.target.value
})
}
render(){
const {
firstName,
lastName
} = this.state;
return (
<div className="">
<Form>
<FormGroup row>
<Label for="firstName" sm={3}>First Name</Label>
<Col sm={9}>
<Input type="text" value={firstName} onChange={(event)=>{this.handleInput(event)}} name="firstName" id="firstName" className="input-lg" />
</Col>
</FormGroup>
<FormGroup row>
<Label for="lastName" sm={3}>Last Name</Label>
<Col sm={9}>
<Input type="text" value={lastName} onChange={(event)=>{this.handleInput(event)}} name="lastName" id="lastName" className="input-lg" />
</Col>
</FormGroup>
</Form>
</div>
);
}
}
My concern here is each time we change an input field, the state changes. This is definitely the standard approach. I am not sure if this is the only approach to handle state. With this approach the component re renders for every character change. Isn't this expensive?
If we have lots of inputs, and lets say we need to calculate character count on some of the input fields then the count value needs some time to appear obviously because of the heavy re rendering.
You are right, re-rendering can be expensive.
You could avoid re-renders with uncontrolled inputs: https://reactjs.org/docs/uncontrolled-components.html
I would recommand to prefer uncontrolled inputs and only use controlled inputs if some values in your form depends on each others

Using objects in hooks for forms

I have just started with REACT and is looking for a valid approach when working with forms with many controls. And i dont need any validation so I skipped react-hook-forms.
Is there any recommended practices when working with forms? I have around 20 inputs so it is a semi large form. I started out having a hook for each input, but realized fast it was a little hard to maintain. So I changed to having one hook with an object like this:
https://codesandbox.io/s/bind-input-kzzd3
I can see some people recommend reducers for forms, is there anything wrong just using a hook with an object like my sample for simplicity? What will a reducer give me extra? Or would you go for a hook for each input?
import React, { useState } from "react";
import "./styles.css";
import { Row, Col, Form, FormGroup, Label, Input, Button } from "reactstrap";
export default function App() {
const onSearch = val => {
alert(val);
};
return (
<div className="App">
<GetSearchForm onSearch={onSearch} />
</div>
);
}
const GetSearchForm = props => {
const [val, setVal] = useState();
const [formData, setFormData] = useState({
accountId: 0,
firstName: "",
lastName: ""
});
const onSearch = event => {
event.preventDefault();
props.onSearch(val);
};
return (
<Form onSubmit={onSearch}>
<Row>
<Col>
<FormGroup>
<Label for="exampleEmail">Account Id</Label>
<Input
type="number"
name="account"
id="account"
placeholder="AccountId"
onChange={e =>
setFormData({ ...formData, accountId: e.target.value })
}
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col>
<FormGroup>
<Label for="firstName">Firstname</Label>
<Input
type="text"
name="firstName"
id="firstName"
placeholder="Firstname"
value={val}
onChange={e => setVal(e.target.value)}
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col>
<FormGroup>
<Label for="exampleEmail">Lastname</Label>
<Input
type="text"
name="lastName"
id="lastName"
placeholder="Lastname"
value={val}
onChange={e => setVal(e.target.value)}
/>
</FormGroup>
</Col>
</Row>
<Button>Submit</Button>
<div>{JSON.stringify(formData, null, 2)}</div>
</Form>
);
};
i dont need any validation so I skipped react-hook-forms.
I assume that you are using a hook means React.useState. Yes, if you don't want to use other form library, it is perfectly fine to update your state that way. Just be careful when you are updating state in useEffect, there is a problem with closure, where state is not updated properly.
any recommended practices when working with forms
As mentioned above, be careful with closure, (state is not updated properly) I will advice to use a setState((prevState)=>return your modified state).
What will a reducer give me extra? Or would you go for a hook for each input?
useReducer has more control for you to setState.
I have around 20 inputs so it is a semi large form.
Best is to use a form library instead of making each an independent state. I have used formik, it doesnt require a state management to use, pre-built validation with or without yup. (when your form grows, which in your case >20 inputs)

Resources