I have spent more than a day on what is probably going to be a very simple solution. I am using react-bootstrap.
I have a form that looks like this:
modal_form.js.jsx
...
render: function() {
var Modal = require('react-bootstrap').Modal;
var InputField = app.InputField
var formElementsCollection= this.props.formElements.map((formElement, i) => {
return (
<InputField
inputControlID="myInput"
inputLabel="Input Label"
placeholderText="My Placeholder"
key={i}
/>
)
});
return (
<Modal
keyboard={true}
show={this.props.showModal}
onHide={this.props.unloadModal}
>
<Modal.Header closeButton>
<h3>
{this.props.headerText}
</h3>
</Modal.Header>
<Modal.Body>
<form>
{formElementsCollection}
<button type="button" className="btn btn-info btn-fill pull-right" onClick={() => this.props.onClickHandler(this)}>{this.props.buttonText}</button>
</form>
<div className="clearfix"></div>
</Modal.Body>
</Modal>
);
}
...
input_field.js.jsx
...
handleChange(e) {
this.setState({ value: e.target.value });
},
render: function() {
var FormGroup = require('react-bootstrap').FormGroup;
var ControlLabel = require('react-bootstrap').ControlLabel;
var FormControl = require('react-bootstrap').FormControl;
return (
<FormGroup
controlId={this.props.inputControlID}
>
<ControlLabel>{this.props.inputLabel}</ControlLabel>
<FormControl
type="text"
value={this.state.value}
placeholder={this.props.placeholderText}
onChange={this.handleChange}
/>
<FormControl.Feedback />
</FormGroup>
);
}
...
As you can see, I am mapping out an array of values in my ModalForm parent and the creating an InputField child for each mapped value of the array. In the InputField child component, I then assign the value state of each element by making use of an 'onChange' method callback.
What I'm struggling to understand, is how to get the value of each of the InputField child components in my parent component? Since I'm using react-bootstrap and it's FormControl component, I do not have a 'ref' attribute to call. I'm not sure this is something that I need to explicitly define, but since the react-bootstrap docs don't provision for this attribute, I figured that there must be a better way of obtaining each value? Am I wrong in this assumption?
Related
I am developing a React frontend to send form data back to my API. I have decided to use formik to create the required forms for my app. While testing the array field and trying to add validation errors only at the in question array element input field. I have run into this error.
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info:
I have successfully implemented my form and errors for other fields except the array field, so I tried to add the ErrorMessage tag with the correct array element index as the name and this is when the error showed its fangs first.
Here is my component code:
I tried to dig into the error and find the solution my self, but all the other stack overflow answers I saw discussing this error were too complicated for me to understand. If anyone can help would be much appreciated also if you have any tips on how I could clean up this code I'll take it its not the prettiest.
import { useDispatch } from "react-redux";
import {Formik, Form, Field, FieldArray, ErrorMessage} from 'formik'
import * as Yup from 'yup'
const Sign = () => {
const ciscoDomainRegex = new RegExp('.*\.cisco\.com')
const SignSchema = Yup.object({
hostname:Yup.string().matches(ciscoDomainRegex, 'Hostname Must Be A Cisco Domain').required('required'),
sans:Yup.array().of(Yup.string().matches(ciscoDomainRegex)),
csr:Yup.mixed()
})
const dispatch = useDispatch()
const showError = (errors, field)=>{
switch (field){
case 'hostname':
return <p>{errors.hostname}</p>
case 'sans':
return <p>{errors.sans}</p>
case 'csr':
return <p>{errors.csr}</p>
default:
return false
}
}
return (
<div>
Sign Page
<Formik
initialValues={{
hostname:'',
sans:[''],
csr:null
}}
validationSchema={SignSchema}
onSubmit={(values)=>console.log(values)}
>
{({errors, touched, setFieldValue})=>{
return(
<Form className="form-center">
<Field className="form-control mt-1" name='hostname' placeholder="Enter Hostname"/>
{/* {errors && touched.hostname ? showError(errors, 'hostname') : null} */}
<ErrorMessage name="hostname"/>
<FieldArray name="sans" placeholder='Enter Sans'>
{({push, remove, form})=>{
const {sans} = form.values
return (
<div>
{
sans.map((san, i)=>{
return (
<div className="input-group" key={i}>
<Field className="form-control mt-1" name={`sans${i}`} placeholder="Enter San"/>
{/* {errors && touched.sans ? showError(errors, 'sans') : null} */}
<ErrorMessage name={`sans${i}`}/>
<div className="input-group-append">
<button className="btn btn-secondary float-end" type="button" onClick={()=>remove(i)}>-</button>
<button className="btn btn-secondary" type="button" onClick={()=>push('')}>+</button>
</div>
</div>
)
})
}
</div>
)
}}
</FieldArray>
<input className="form-control mt-1" type="file" name='csr' onChange={(e)=>setFieldValue('csr',e.currentTarget.files[0])}/>
{errors && console.log(errors)}
<button className="btn btn-primary" type="submit">Submit</button>
</Form>
)
}}
</Formik>
</div>
);
}
export default Sign;
You are passing the wrong field name for each of the fields that will be added dynamucally.
Each field name should be name[index] like so...
<Field name={san[$(index)]/>
or u can as well use the dot notation to reference the particular field in the field array
PS: don't forget to use backticks
I using useFieldArray from react-hooks-form. But when i append new field with a required rules; required error doesnt show on my screen when i submit it. But after that , if i append new field then delete it then i can see that error on my screen. So its not working in first render. So what do i making wrong ? How can i solve it ? My codes :
const { fields, append, remove } = useFieldArray(
{
control,
name: "Taxes",
}
);
Selecting something from select and finding my model from my list then appending it as a input :
const value=e.target.value;
const model = otherTaxesList.find((x) => x.Id === value);
append(model);
FormInput is simply a Controller with a material UI OutlinedInput
{
fields.map((item, index, arr) => {
return (
<React.Fragment key={item.id}>
<div className="flex ">
<FormInput
className="flex-1"
control={control}
name={`Taxes[${index}].model`}
label={item.Name}
type="number"
rules={{ required: customRules.required }}
error={errors.Taxes?.[index]?.model && errors.Taxes?.[index]?.model}
placeholder={item.Name}
/>
<button type="button" onClick={() => remove(index)}>
Delete
</button>
</div>
</React.Fragment>
);
});
}
Alright I am trying to submit two different forms as independent components in another page
component where I only have one button to submit the data of both forms.
So I am struggling to have a shared state in the page component and I need to pass the whole state of each form component to my page component on submit.
Can anyone recommend a best practice for my use case ?
render() {
return (
<div as={Row} className="container" style={formStyle}>
<Col>
<Form onSubmit={this.submitData}>
<TripForm />
<PostForm heading="add your first blog entry" />
<Button variant="dark" type="submit">
Summing up
</Button>
</Form>
</Col>
</div>
);
}
define your state in the parent component and pass it down in props
class PageComponent = {
state = { } //define your state here
handleChange = () => {} // define a function that handles changing state
submitData = () => {
// in here you can access this.state and then submit form data with that state
}
render() {
return (
<div as={Row} className="container" style={formStyle}>
<Col>
<Form onSubmit={this.submitData}>
<TripForm handleChange={handleChange} someState={someState} />
<PostForm heading="add your first blog entry" handleChange={handleChange} someState={someState}/>
<Button variant="dark" type="submit">
Summing up
</Button>
</Form>
</Col>
</div>
);
}
}
I've also defined someState which you can pass down as props to the child/form components. once you set state in there with handleChange it will set state in the parent component and you can submitData with that state
I am new to React and I'm not sure what would be the best approach to take.
I have a modal component to be displayed once the user fills out the values inside the form and click on Preview Voucher to print those values inside the modal.
I tried this code and below I have the Preview Voucher component with a constructor and events.
// PreviewVoucher.js
class PreviewVoucher extends React.Component {
constructor(props) {
super(props);
this.state = {
voucherNumber: "0",
//
addModalShow: false
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSelectChange = this.handleSelectChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(e) {
const target = e.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const inputName = target.name;
this.setState({
[inputName]: value
});
}
handleSelectChange(e) {
this.setState({ labelSize: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
}
}
And I want to render this form on the page that has a child component Modal Voucher
// PreviewVoucher.js
render() {
let addModalClose = () => this.setState({ addModalShow: false });
return (
<form onSubmit={this.handleSubmit}>
<div>
<p>Number of vouchers to create:</p>
<input
min="0"
type="number"
name="voucherNumber"
value={this.state.voucherNumber}
onChange={this.handleInputChange}
/>
</div>
<div>
<h4>Message:</h4>
<p>Some message</p>
<ButtonToolbar>
<Button onClick={() => this.setState({ addModalShow: true })}>
Preview Voucher
</Button>
<ModalVoucher
show={this.state.addModalShow}
onHide={addModalClose}
/>
</ButtonToolbar>
</div>
<input type="submit" value="Create voucher" />
</form>
);
}
And this is the child component - Modal Voucher that would contain some text and would like to display the dynamic values from the Preview Voucher component inside the < Modal Body >.
// ModalVoucher.js
class ModalVoucher extends React.Component {
render() {
return (
<Modal
{...this.props}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
Voucher preview
</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>{/* update code here */}</div>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.props.onHide}>Close</Button>
</Modal.Footer>
</Modal>
);
}
}
How to render parent's state within child component
You will need to pass the state value from PreviewVoucher into its child ModalVoucher as a prop.
// PreviewVoucher.js
render() {
<ModalVoucher
show={this.state.addModalShow}
onHide={addModalClose}
voucherNumber={this.state.voucherNumber}
/>
}
Then use it inside of ModalVouchers render method.
// ModalVoucher.js
render() {
<Modal.Body>
<div>{this.props.voucherNumber}</div>
</Modal.Body>
}
I put together this sandbox showing this in action.
Other suggestions
After making this change, you might see this error in the console
Warning: React does not recognize the `voucherNumber` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `vouchernumber` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
This occurs because you are passing all the props directly into the Modal, which
the underlying library passes to a DOM element.
<Modal
{...this.props}
Because of this, I think it is usually a bad idea to directly forward all props
and I prefer to pass specific props instead.
<Modal
show={this.props.show}
onHide={this.props.onHide}
New to React JS here and trying to make a form (that's in a modal) to save the submit of a text input and text area to local storage. The value of these 2 input are mapped to my Form State. My Form Component has a button with onSubmit function that should send the object to local storage, once the user clicks it. But what happens is that it saves every letter that I type as a separate entry to local storage and not the whole thing when user clicks the button. What am I doing wrong here?
So suppose I want to save {name: "oatmeal", ingredients: ["milk, "oat"]}, it saves:
{name: "o", ingredients: []}
{name: "oa", ingredients: []}
{name: "oat", ingredients: []}
etc. as I type....
Here is my code:
var App = React.createClass({
render: function(){
return(
<div className="container">
<h1>Recipe Box</h1>
<AddRecipeButton />
</div>
)
}
});
var AddRecipeButton = React.createClass({
getInitialState(){
return {showModal: false};
},
close: function(){
this.setState({showModal:false});
},
open: function(){
this.setState({showModal:true});
},
render: function(){
return (
<div>
<Button bsStyle="warning" bsSize="large" onClick={this.open}>
Add Recipe
</Button>
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>Add a Recipe Here</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form />
</Modal.Body>
</Modal>
</div>
)
}
});
var Form = React.createClass({
getInitialState(){
return {
name: "",
ingredients: [ ]
};
},
getValidationStateName(){
var length = this.state.name.length;
if(length > 0) {
return "success";
} else {
return "error";
}
},
getValidationStateIngredients(){
var length = this.state.ingredients.length;
if(length > 0){
return "success";
} else {
return "error";
}
},
handleInput: function(key,e){
var input = e.target.value;
if(key === "ingredients"){
input = e.target.value.split(",");
}
var update = {};
//create object to merge with state
update[key] = input;
//set the value of input field to update object
console.log(update);
this.setState(update, function(){
//merge the object into the state
console.log(this.state);
});
},
handleSubmit(){
var recipe = JSON.stringify(this.state);
localStorage.setItem(this.state.name, recipe);
},
render: function(){
return (
<form>
<FormGroup controlId="formNameText" validationState = {this.getValidationStateName()}>
<ControlLabel>Recipe</ControlLabel>
<FormControl
type="text"
placeholder="Give your recipe a name"
value={this.state.name}
onInput={this.handleInput.bind(this,'name')}
/>
<FormControl.Feedback />
</FormGroup>
<br/>
<FormGroup controlId="formIngredientsTextarea" validationState = {this.getValidationStateIngredients()}>
<FormControl
componentClass="textarea"
placeholder="Insert your ingredients, separated by a comma"
value={this.state.ingredients}
onInput={this.handleInput.bind(this,'ingredients')}
/>
<FormControl.Feedback />
<hr/>
</FormGroup>
<Button bsStyle="primary" onSubmit={this.handleSubmit()}>Submit</Button>
</form>
);
}
});
ReactDOM.render(<App />, document.getElementById('app'));
Try changing <Button bsStyle="primary" onSubmit={this.handleSubmit()}>Submit</Button> to <Button bsStyle="primary" onSubmit={this.handleSubmit.bind(this)}>Submit</Button>
When you do this,
<Button bsStyle="primary" onSubmit={this.handleSubmit()}>Submit</Button>
You will actually invoke the function handleSubmit() on each render, instead you should only pass the function prototype.
<Button bsStyle="primary" onSubmit={this.handleSubmit}>Submit</Button>
Nota Bene: You won't need .bind(this) here on component methods (unless you want to invoke them with parameters .bind(this, params)) when your component is created using React.createClass; the component methods are auto bound by React.