How to prevent setState changing input field? - javascript

I am using ant design form and trying to prevent clearing input after I check a checkbox.
Here is the code :
this.state = {
externalOptionsArray: [],
}
// onClick function
optionOnChange = (e, index, array) => {
const { externalOptionsArray } = this.state
let externalOptionCurrentState = externalOptionsArray
externalOptionCurrentState[index].is_enabled = !externalOptionCurrentState[index].is_enabled;
this.setState({
externalOptionsArray: externalOptionCurrentState
})
}
// Components
<Form onSubmit={this.onSubmit}>
<FormContainerWithDescription
direction="vertical"
title="Product"
description="Product Information">
<FormItemRow>
<Col span={24} style={colStyle}>
<FormItem label={'Product name'} colon={false} style={{ marginBottom: 0 }}>
{getFieldDecorator('title', {
rules: [
{ required: true, message: 'name is required' },
],
})(<Input />)}
</FormItem>
</Col>
</FormItemRow>
<FormItemRow>
<Col span={24} style={colStyle}>
<FormItem label={'external_options'} colon={ false } style={{ marginBottom: 0 }}>
{ externalOptionsArray.map((option, index, array) => {
return (
<Col style={{ float: 'left', width: '50%' }} key={option.name}>
<Row>
<Checkbox defaultChecked={option.is_enabled} onChange={() => this.optionOnChange(index, array)}>{option.name}</Checkbox>
</Row>
</Col>
)
})}
</FormItem>
</Col>
</FormItemRow>
</FormContainerWithDescription>
</Form>
And here is the image for better understanding
The problem is when I input a text in a input field and click one of checkboxes, input field automatically cleared up, it goes blank input field again.
I believe this is happening due to setState inside of optionOnChange function. Whenever I click one of checkboxes, setState takes place and it rerenders the DOM.
So I used e.preventDefault() and e.stopPropagaion inside of Checkbox component like this below.
<Checkbox defaultChecked={option.is_enabled} onChange={() => this.optionOnChange(index, array)} onClick={e => e.preventDefault()}>{option.name}</Checkbox>
// and
optionOnChange = (e, index, array) => {
e.preventDefault()
e.stopPropagation()
const { externalOptionsArray } = this.state
let externalOptionCurrentState = externalOptionsArray
externalOptionCurrentState[index].is_enabled = !externalOptionCurrentState[index].is_enabled;
this.setState({
externalOptionsArray: externalOptionCurrentState
})
}
But neither of them work
How can I make Checkbox works without rerender the input field?

You need to make it a controlled component. If you set the value of the Input component using a state variable then it remains even after state change.
So looking at your code above, you may need to have another variable like productName in the state and a onChange event handler to set the state when the input is changed
this.state = {
externalOptionsArray: [],
productName: '',
}
onChange = (e) => {
this.setState({productName: e.target.value});
}
And pass this to the Input like
<Input value={this.state.productName} onChange={this.onChange} />

You can store product's name in state and change it when input changes.
this.state = {
externalOptionsArray: [],
productName: '',
}
then define onChange function where you handle updating the state, like you did in optionOnChange()
and pass it to your Input component
<Input onChange={this.onChange} />

Related

More generic way to set an objects value within React handleChange

I have a component in React, I need to take a value from the radio button being checked and then set that value within an object in my Formik values. If a different radio button is selected within the group I need to set the previously selected one to false, is there a standard way of doing this? I'm using an object within my Formik values as the field holds a date as well as the attribute from the radio button, you can see where I place the date into the object using handleTime, so I can't just null the field and place the new item in.
I'm currently doing the following in my component to update the Formik time_frame value.
import React, {Component} from 'react';
import {Col, Row} from 'react-bootstrap';
import Radio from "./radio";
export default class EventTimeFrame extends Component {
state = {
eventTimeFrame: [
{id: 1, value: "one_off", label: "ONE OFF", checked: false},
{id: 2, value: "recurring", label: "RECURRING", checked: false},
]
}
handleOccurance = value => {
let timeCopy = {...this.props.values.time_frame}
if (value.target.checked) {
if (value.target.value === "one_off") {
timeCopy[value.target.value] = true
timeCopy["recurring"] = false
} else {
timeCopy[value.target.value] = true
timeCopy["one_off"] = false
}
}
this.props.onChange("time_frame", timeCopy)
this.setState(prevState => ({
eventTimeFrame: prevState.eventTimeFrame.map(
el => el.value === value.target.value ? {...el, checked: true} : el
)
}))
};
handleTime = value => {
let timeCopy = {...this.props.values.time_frame}
timeCopy["start"] = new Date(value.target.value);
this.props.onChange("time_frame", timeCopy)
};
render() {
return (
<div>
<Row>
<Col>
<h4 className="ui centered question-header text-center">ONE OFF OR RECURRING EVENT?</h4>
</Col>
</Row>
<Row>
{
this.state.eventTimeFrame.map((timeFrame) => {
return (
<Radio name="time_frame" key={timeFrame.value}
onChange={this.handleOccurance} checked={timeFrame.checked} {...timeFrame} />
)
})
}
</Row>
<Row>
<Col>
<h4 className="question-header date-text">PLEASE ENTER THE FIRST DAY OF YOUR EVENT</h4>
</Col>
<Col>
<input type="date" className="form-control date" name="start"
onChange={this.handleTime}/>
</Col>
</Row>
</div>
)
}
}
I feel like there has to be a standard way of dealing with things like this
You're correct by suspecting there's a simpler way 😃 Some feedback:
Don't store the eventTimeFrame in state, a constant will do.
The standard way of using radio input values in Formik is to define one value in initialState for the entire group. Formik will set its value to the selected option.
Storing all options in values would have been fine if you were using checkboxes instead of radio buttons.
You wrote your own custom onChange handlers, but it would be simpler to just use Formik's provided onChange handlers. I only use my own handler if I have to, for example when formatting a value before setting it with setFieldValue()
See below:
Live Demo
// outside of component
const timeFrameTypes = [
{ id: "one_off", label: "ONE OFF" },
{ id: "recurring", label: "RECURRING" }
];
// in component's render function:
<Formik
initialValues={{
time_frame: {
type: "",
start: ""
}
}}
onSubmit={async (values) => {
// do something with values
}}
>
{({ handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<div>
<Row>
<Col>
<h4 className="ui centered question-header text-center">
ONE OFF OR RECURRING EVENT?
</h4>
</Col>
</Row>
<Row>
{timeFrameTypes.map((timeFrameType) => {
return (
<Field
key={timeFrameType.id}
component={Radio}
name="time_frame.type"
id={timeFrameType.id}
label={timeFrameType.label}
/>
);
})}
</Row>
<Row>
<Col>
<h4 className="question-header date-text">
PLEASE ENTER THE FIRST DAY OF YOUR EVENT
</h4>
</Col>
<Col>
<Field name="time_frame.start">
{({ field }) => <input type="date" {...field} />}
</Field>
</Col>
</Row>
</div>
<br />
<button type="submit">Submit</button>
</form>
);
}}
</Formik>

React Js, onChange {setState} event not working

I'm working my practice app using ReactJs and Django. My problem is when I update the Data via Axios "onChange" event not working.
updateForm.js
class Contact extends Component {
constructor(props) {
super(props);
this.state = {
Name: '',
Contact: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleformsubmit = this.handleformsubmit.bind(this);
}
handleformsubmit = (event, requestType, id) => {
const Name = event.target.elements.Name.value;
const Contact = event.target.elements.Contact.value;
switch (requestType) {
case 'put':
return axios.put(`http://127.0.0.1:8000/Contact/${id}/`, {
Name: Name,
Contact: Contact
})
.then(res => console.log(res))
.catch(error => console.log(error));
}
}
handleChange(event) {
this.setState({ Name: event.target.value });
this.setState({ Contact: event.target.value });
}
render() {
const Name = this.state.Item_no;
const Contact = this.state.Supplier;
return (
<Form onSubmit={(event) => this.handleformsubmit(
event,
this.props.requestType,
this.props.id)}>
<div className="container">
<GridContainer component="span">
<Card>
<CardHeader color="primary">
<h4>Update Contact Info</h4>
</CardHeader>
<CardBody>
<GridContainer>
<GridItem sm={12} md={6}>
<CustomInput
labelText="Name"
type="text"
id="Name"
name="Name"
formControlProps={{
fullWidth: true
}}
value={Name}
onChange={this.handleChange}
/>
</GridItem>
<GridItem sm={12} md={6}>
<CustomInput
labelText="Contact"
name="Contact"
type="text"
id="Contact"
formControlProps={{
fullWidth: true
}}
value={Contact}
onChange={this.handleChange}
/>
</GridItem>
I already done searching and tried the possible solution's but nothing works. Nothing wrong with "put" actions but on my onChange event not trigger when I update the data. Help me how to work this.Thank you!
From your code, I can see The onChange function just update the new state and the function handleformsubmit will only be called until you submit the form
It looks like your input value is controlled, which refuses any changes from handleChange
Try value={this.state.Name}

How to bind multiple onChange callback for different inputs?

I am making a different form component using ant design and trying to bind several different inputs
Here is the code :
this.state = {
title: '',
product: '',
options: 0,
price: 0,
}
onTitleChange = (e) => {
this.setState({
title: e.target.value
})
}
onProductChange = (e) => {
this.setState({
product: e.target.value
})
}
onHandleChange = value => {
this.setState({
priceOption: value
})
}
onNumberChange = e => {
this.setState({
price: e.target.value
})
}
<FormItemRow>
<Col span={24} style={colStyle}>
<FormItem label={'title'} colon={false} style={{ marginBottom: 0 }}>
{getFieldDecorator('title', {
rules: [
{ required: true, message: 'title is required' },
],
})(<Input onChange={this.onTitleChange}/>)}
</FormItem>
</Col>
</FormItemRow>
<FormItemRow>
<Col span={24} style={colStyle}>
<FormItem label={'product-number'} colon={false} style={{ marginBottom: 0 }}>
{getFieldDecorator('product-number', {
rules: [{ required: true, message: 'product-number is required' }],
})(<Input onChange={this.onProductChange}/>)}
</FormItem>
</Col>
</FormItemRow>
<FormItemRow>
<Col span={12} style={colStyle}>
<FormItem label={'options'} colon={false} style={{ marginBottom: 0 }}>
{getFieldDecorator('options', {
rules: [
{ required: true, message: 'options is required' },
],
})(<Select onChange={this.onHandleChange}>{this.handWashOptions(this.props.handWashOptions)}</Select>)}
</FormItem>
</Col>
<Col span={12} style={colStyle}>
<FormItem label={'price'} colon={false} style={{ marginBottom: 0 }}>
{getFieldDecorator('price', {
rules: [
{ required: true, message: 'price is required' },
],
})(<Input type="number" onChange={this.onNumberChange}/>)}
</FormItem>
</Col>
</FormItemRow>
title and product using just Input component.
option is using Select component.
and price is using Input type number component.
I think it is very inefficient using different onChange callback on each input component.
Is there any way I can bind one onChange callback function?
You can simply make a generic function handleChange, pass the name and value which are to be updated
handleChange(name,value){
this.setState({[name]: value})
}
and pass value to handle change like this
<Input onChange={(name,value)=>this.onTitleChange(name,value)}/>
You can use e.target.value to get value from target, also if you need to have different logic for some particular element then you can simply add a exception in handleChange
this.state = {
fields:{
each:
field:
you:
have:
}
}
handleChange = (event) => {
const { value, type, name } = event.currentTarget
console.log(type, value, name);
this.setState(prevState => ({
fields: {
...prevState.fields,
[name]: type === 'number' ? parseInt(value, 10) : value
}
}));
};
As stated in Handling Multiple Inputs,
add a name attribute to each element and let the handler function choose what to do based on the value of event.target.name.
So you can have:
onChange = event => {
const name = event.target.name;
this.setState({
[name]: event.target.value
})
}
// Add `name` attribute used for event.target.name
<Select onChange={this.onChange} name="wash">...</Select>
<Input type="number" onChange={this.onChange} name="number"/>
<Input onChange={this.onChange} name="product" />

React - Fill array in state with input value

I have an empty link array and three input fields on my page.
I want to get the values of all three input fields in one array in my state variable link[] so that I can send a link array to my DB.
Like you see I am using semantic ui but for the input fields there is just the onChange function I can use
How can I do that?
export default class Dashboard extends Component {
constructor() {
super();
this.state = {
link : []
};
render() {
return (
<Grid style={{ height: "100%" }}>
<Grid.Column width={6} className='dashboardCard'>
<Card fluid>
<Card.Content >
<Card.Header>Allgemeine Künstler-Daten</Card.Header>
<Card.Description>
"Strange Fruit" is a song performed most famously by Billie
Holiday, who first sang and recorded it in 1939.
</Card.Description>
</Card.Content>
<Card.Content extra textAlign="center">
<Form onSubmit={this.handleSubmit} >
<Input
fluid
label="Social Link 1"
placeholder="www.twitter.com/artist"
style={{ margin: "1rem" }}
width={6}
name="link"
value={this.state.link}
onChange={this.handleInputChange}
/>
<Input
fluid
label="Social Link 2"
placeholder="www.soundcloud.com/artist"
style={{ margin: "1rem" }}
width={6}
name="link"
value={this.state.link}
onChange={this.handleInputChange}
/>
<Input
fluid
label="Social Link 3"
placeholder="www.facebook.com/artist"
style={{ margin: "1rem" }}
width={6}
name="link"
value={this.state.link}
onChange={this.handleInputChange}
/>
<Dropdown
<Form.Button positive
value="Submit">Save</Form.Button>
</Form>
</Card.Content>
</Card>
</Grid.Column>
<Grid.Column columns={5} />
</Grid>
);
}
I would recommend not storing the links in an array but as separate values with distinct keys in your component's state. That way you'll know which link to update if the user changes something. Just have keys in the state that correspond to inputs that are rendered, using the <input> name attribute. It's also convenient to wrap all of the data from the form in a formOptions key in the state in case you have other state data.
You can do something like this:
constructor() {
super();
this.state = {
formOptions: {
link1: '',
link2: '',
link3: ''
}
};
}
handleInputChange = event => {
const { name, value } = event.target;
const { formOptions } = this.state;
formOptions[name] = value;
this.setState({ formOptions });
}
Then just give your inputs the name attributes link1, link2, and link3.
Then in your handleSubmit method, you can convert the separate links into a single array if that's how you need to send them.
Note that the method handleInputChange needs to be written as an attribute assigned to an arrow function for binding this to the component so that the state can be accessed. Alternatively you can just define handleInputChange normally and then in each input's onChange put this.handleInputChange.bind(this), but I find the first way a bit cleaner.

shows error on all topic name field

I have a form where user gets to post the topics. Beneath that the posted topic is shown. When there is the validation error in one of the topic name or type field then all the name or type field is shown with name or topic required. why is that so? The validation in onChange event is not behaving normally.
Here is the code
This if for the validation
export const validate = values => {
const errors = {}
const requiredFields = {
topic_name: 'Topic Name',
topic_type: 'Topic Type',
}
for (const key in requiredFields) {
if(requiredFields.hasOwnProperty(key)) {
if(!values[key]) {
errors[key] = `${requiredFields[key]} is required`
}
}
}
return errors
}
topic
class Topic extends React.Component {
constructor(props) {
super(props);
this.state = {
customTopic: [],
visible: false,
id: '',
topic_name: this.props.match.params.topic || '',
topic_type: ''
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.props.fetchCustomTopic();
}
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
simplePlainTable = topics => (
<DataTable plain>
<TableHeader>
<TableRow>
<TableColumn>Topic Name</TableColumn>
<TableColumn>Topic Type</TableColumn>
<TableColumn>Action</TableColumn>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableColumn>
<Field
placeholder="Topic Name"
name="topic_name"
id="topic_name"
value={this.state.topic_name}
onChange={this.handleChange}
className="input-field"
type="text"
fullWidth
component={TopicNameField}
required
/>
</TableColumn>
<TableColumn>
<Field
placeholder="Topic Type"
name="topic_type"
id="topic_type"
value={this.state.topic_type}
onChange={this.handleChange}
required
className="input-field"
type="text"
fullWidth
component={TopicTypeField}
/>
</TableColumn>
<TableColumn>
<Button
icon
tooltipLabel="Add"
onClick={e => this.addCustomTopic(e)}>
add
</Button>
</TableColumn>
</TableRow>
{topics &&
topics.customTopic.map((obj, i) => (
<TableRow key={i} id={obj.id}>
<TableColumn>
<Field
placeholder="Topic Name"
name="topic_name"
id="topic_name"
defaultValue={obj.topic_name}
onBlur={e =>
this.updateCustomTopic(
{ topic_name: e.target.value },
obj.id
)
}
required
className="input-field"
type="text"
fullWidth
component={TopicNameField}
/>
</TableColumn>
<TableColumn>
<Field
placeholder="Topic Type"
name="topic_type"
id="topic_type"
defaultValue={obj.topic_type}
onBlur={e =>
this.updateCustomTopic(
{ topic_type: e.target.value },
obj.id
)
}
required
className="input-field"
type="text"
fullWidth
component={TopicTypeField}
/>
</TableColumn>
<TableColumn>
<Button
icon
tooltipLabel="Delete"
onClick={() => this.show(obj.id)}>
delete
</Button>
</TableColumn>
</TableRow>
))}
</TableBody>
</DataTable>
);
render() {
const { topics } = this.props;
return (
<div className="container">
{this.simplePlainTable(topics)}
{this.dialogContainer()}
</div>
);
}
}
export default reduxForm({
form: 'wizard',
validate
})(connect(mapStateToProps, mapDispatchToProps)(Topic));
I am using redux form.
this is what happening
What you're seeing occurs because both Topic instances are, technically, the same "form". When you call reduxForm(), you pass in a form name (wizard). This acts as the form's "identifier" and the form's data is stored in redux using this identifier. In other words, when you have two instances of your component being rendered, both instances are sharing the same exact form state in redux.
The way to solve this really depends on your business requirements. Are you rendering multiple Topics at the same time? If so, then you probably want to look into FieldArray or FormSection. If not, then the state from your first Topic form probably just isn't being destroyed before the second Topic form is displayed.

Categories

Resources