Grabbing React form field values for submission - reactjs

Given a React form, I'm having trouble getting the value from the selected radio button, and text box if other is selected. I should be able to pass the fields into the send() for the post, but not sure how to grab them.
class CancelSurvey extends React.Component {
constructor (props) {
super(props)
this.state = {
reasons: [],
reason: {}
}
this.processData = this.processData.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.otherSelected = this.state.reason === "otheroption";
}
componentDidMount () {
this.fetchContent(this.processData)
}
/**
* Fetch reasons
*/
fetchContent (cb) {
superagent
.get('/api/user/survey')
.then(cb)
}
/**
* Set state after reasons have been fetched
* #param data
*/
processData (data) {
this.setState({
reasons: data.body
})
}
handleSubmit (e) {
e.preventDefault()
let reason = this.state.reason
if (reason === 'otheroption') {
reason = this.state.otherreason
}
console.log(reason)
superagent
.post('/api/user/survey')
.send({
optionId: this.state.reason.reason_id,
optionText: this.state.reason.client_reason,
otherReasonText: this.state.otherreason
})
.then(function (res) {
console.log('Survey Sent!')
})
}
/**
* render
*/
render (props) {
const content = this.props.config.contentStrings
const reason = this.state.reasons.map((reason, i) => {
return (
<div className='fieldset__item' key={i}>
<label>{reason.client_reason}</label>
<input type='radio'
id={reason.reason_id}
value={reason.client_reason}
name='reason'
checked={this.state.reason.reason_id === reason.reason_id}
onChange={() => this.setState({reason})} />
</div>
)
})
return (
<div className='survey'>
<h2 className='heading md'>{content.memberCancel.exitSurvey.heading}</h2>
<p className='subpara'>{content.memberCancel.exitSurvey.subHeading}</p>
<form id='exit-survey' onSubmit={this.handleSubmit}>
<fieldset className='fieldset'>
{ reason }
<label>Other reason not included above:</label>
<input type='radio'
id='otheroption'
name='reason'
value={this.state.reason.otherreason}
onChange={() => this.setState({reason:{reason_id: 70, client_reason: 'other'}})} />
<input className='valid'
type='text'
id='otheroption'
name='othertext'
placeholder={content.memberCancel.exitSurvey.reasonPlaceholder}
onChange={(event) => this.setState({otherreason: event.target.value})} />
</fieldset>
<div className='footer-links'>
<button className='btn btn--primary btn--lg' onClick={this.handleSubmit}>{content.memberCancel.exitSurvey.button}</button>
</div>
</form>
</div>
)
}
}
export default CancelSurvey

Your variables aren't correct. I've update them to what I think is correct.
handleSubmit (e) {
e.preventDefault()
superagent
.post('/api/user/survey')
.send({
optionId: this.state.reason.reason_id,
optionText: this.state.reason.client_reason,
otherReasonText: this.state.reason.otherreason
})
.then(function (res) {
console.log('Survey Sent!')
})
.catch(function (err) {
console.log('Survey submission went wrong...')
})
}
/**
* render
*/
render (props) {
const content = this.props.config.contentStrings
const reason = this.state.reasons.map((reason, i) => {
return (
<div className='fieldset__item' key={i}>
<label>{reason.client_reason}</label>
<input
type='radio'
id={reason.reason_id}
name='reason'
checked={this.state.reason.reason_id === reason.reason_id}
value={reason.client_reason}
onChange={() => this.setState({reason})} />
</div>
)
})
return (
<div className='survey'>
<h2 className='heading md'>{content.memberCancel.exitSurvey.heading}</h2>
<p className='subpara'>{content.memberCancel.exitSurvey.subHeading}</p>
<form id='exit-survey' onSubmit={this.handleSubmit}>
<fieldset className='fieldset'>
{ reason }
<label>Other reason not included above:</label>
<input type='radio'
id='otheroption'
name='otheroption'
value={this.state.reason.otherreason}
checked={this.state.reason.reason_id === 0}
onChange={() => this.setState({ reason: {reason_id: 0, client_reason: ""} })} />
<input className='valid'
type='text'
id='othertext'
name='othertext'
value={this.state.reason.otherreason}
placeholder={content.memberCancel.exitSurvey.reasonPlaceholder}
onChange={(event) => this.setState({ reason: {reason_id: 0, client_reason: "", otherreason: event.target.value} })} />
</fieldset>
<div className='footer-links'>
<button className='btn btn--primary btn--lg' onClick={this.handleSubmit}>{content.memberCancel.exitSurvey.button}</button>
</div>
</form>
</div>
);
}

Related

How to fix "cannot update during an existing state transition (such as within `render`)" problem?

I get this problem even though my code doesn't update the state directly.
Here's my code:
export class Login extends React.Component {
static contextType = getContext();
_usernameValue = "";
_passwordValue = "";
// adding value to notification variable activates notification
state = { mode: "signin", notification: "" };
constructor(props) {
super(props);
this.showNotificationDeb = debounceOnce(this.showNotification, 200);
this.closeNotificationDeb = debounceOnce(this.closeNotification, 200);
}
signinClicked = () => {
this.context.dispatch({
type: "LOGIN",
payload: {
username: this._usernameValue,
password: this._passwordValue,
callback: this.incorrectCredentials,
},
});
};
forgotPasswordClicked = () => {
this.setState({ mode: "password" });
};
sendPasswordRequest = () => {
this.context.dispatch({
type: "FORGOT_PASSWORD",
payload: {
username: this._usernameValue,
password: this._passwordValue,
callback: this.passwordRequestSent,
},
});
};
incorrectCredentials = (errorMessage) => {
this.showNotificationDeb(errorMessage);
};
passwordRequestSent = (message) => {
this.showNotificationDeb(message);
};
restoreSigninWindow = () => {
this.setState({ mode: "signin" });
};
showNotification = (message) => {
console.log("aa");
this.setState({ notification: message });
};
closeNotification = () => {
if (this.state.notification) this.setState({ notification: "" });
};
render() {
return (
<div className={styles.container}>
<div className={styles.loginContainer}>
<Icon
rotate={90}
path={mdiCog}
size={2}
color="black"
className={styles.loginIcon}
/>
<p className={styles.loginTitle}>Sign in to RAS</p>
<div
className={`${styles.notificationContainer} ${
this.state.notification ? "" : styles.hideNotification
}`}
>
<p className={styles.notificationMessage}>
{this.state.notification}
</p>
<p
className={styles.notificationCloseButton}
onClick={() => this.closeNotification()}
>
x
</p>
</div>
<div className={styles.loginWindow}>
{this.state.mode === "signin" ? (
<React.Fragment>
<label className={styles.inputLabel}>Username</label>
<input
id="usernameInput"
className={styles.input}
onChange={(event) => {
this._usernameValue = event.target.value;
}}
></input>
<div className={styles.passwordLabelContainer}>
<label className={styles.inputLabel}>Password</label>
<p
className={styles.forgotPasswordLabel}
onClick={() => this.forgotPasswordClicked()}
>
Forgot password?
</p>
</div>
<input
id="passwordInput"
type="password"
className={styles.input}
onChange={(event) => {
this._passwordValue = event.target.value;
}}
></input>
<Button
variant="contained"
className={styles.button}
onClick={() => this.signinClicked()}
>
Sign in
</Button>
</React.Fragment>
) : (
<React.Fragment>
<div className={styles.backButtonContainer}>
<div onClick={() => this.restoreSigninWindow()}>
<Icon
path={mdiKeyboardBackspace}
size={0.85}
color="black"
className={styles.backIcon}
/>
<p>Back</p>
</div>
</div>
<label className={`${styles.inputLabel}`}>
Enter your email address. Password reset link will be send to
your email address.
</label>
<input
id="usernameInput"
className={styles.input}
placeholder="Enter your email address"
></input>
<Button
variant="contained"
className={styles.button}
onClick={() => this.sendPasswordRequest()}
>
Send
</Button>
</React.Fragment>
)}
</div>
</div>
</div>
);
}
}
As you see, I don't change the state directly. I do it always by calling the functions above. However, according to the React I am changing the state from inside render() function.
I do use lambda functions but I still see the problem.

How Can I get first element from API array

I am learning React and API. Here I am fetching data from API.I am trying to do on click button one user should appear. Or on click of user name all other user info should appear. I want to display one element from API array.If click on button new user should show. How to get only one user or one user info. Botton I added input box which can shoe only one value. I am stuck here.
import React from 'react';
import './App.css';
class Home extends React.Component {
constructor(props) {
super(props)
this.state = {
items: [],
error: '',
email:'',
phone:'',
companyName:''
}
this.handleInputChange = this.handleInputChange.bind(this);
this.showUserEmail = this.showUserEmail.bind(this);
}
handleInputChange(event){
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSelectUserName = (event) => {
console.log(event.target.value);
const myUser = (event.target.value);
this.setState({ name: event.target.value });
const selectedUser= event.target.value;
}
showUserEmail=(e)=>{
console.log("you are clicking name" );
this.setState({
})
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((result) => {
console.log(result);
console.warn(result);
this.setState({ items: result });
});
}
render() {
const { items } = this.state
return (
<div>
<button className="btn"> Show new User</button>
<div className="new-user" onChange={event => this. handleSelectNewUser(event)}>
{this.state.items.map(items => (
<span key={items.name} value={items.name}>
{items.name} <p></p></span>))}
</div>
<p className="para-text"> Data from API</p>
<div className="user-info">
{
items.length ?
items.map(items => <div key ={ items.id }>
<div className="user-details"> {items.name} </div>
<div className="user-details">{items.phone}</div>
<div className="user-details">{items.company.name}</div>
<div className="user-details">{items.username}</div>
<div className="user-details">{items.email}</div>
<div className="user-details">{items.website}</div>
</div>) : null
}
</div>
<h2>Find User By Username</h2>
<div className="input-box">
<select onChange={event => this.handleSelectUserName(event)}>
{this.state.items.map(items => (
<option key={items.name} value={items.name}>
{items.username}
</option>
))}
</select>
{/* Auto select */}
<div className=" Show-User-Auto">
<div className="input-box">
<input type="text"
placeholder=" Auto Select"
required="required"
onChange={event => this.handleInputChange(event)}
value={this.state.name} />
</div>
</div>
</div>
</div>
);
}
}
export default Home;
You have to maintain a state which contains userId , then you can condition render it like below code
import React from "react";
class Users extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
error: "",
email: "",
phone: "",
companyName: "",
userId: "",
orgList: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.showUserEmail = this.showUserEmail.bind(this);
}
handleSetUserId = (userId) => {
if (userId === this.state.userId) {
this.setState({ userId: "" });
} else {
this.setState({ userId });
}
};
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSelectUserName = (event) => {
console.log(event.target.value);
const myUserId = event.target.value;
if (myUserId) {
console.log(this.state.orgList, "this.state.orgList");
let selectedUser = this.state.orgList.filter((item) => {
return item.id == Number(myUserId);
});
console.log(selectedUser, "selectedUser");
this.setState({ items: selectedUser });
}
};
showUserEmail = (e) => {
console.log("you are clicking name");
this.setState({});
};
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((result) => {
console.log(result);
console.warn(result);
this.setState({ items: result, orgList: result });
});
}
handleFetchAllUsers=()=>{
this.setState({items:this.state.orgList})
}
render() {
const { items } = this.state;
return (
<div>
<button className="btn" onClick={()=>this.handleFetchAllUsers()}> Show All User</button>
<div
className="new-user"
onChange={(event) => this.handleSelectNewUser(event)}
>
{this.state.items.map((items) => (
<span key={items.name}>
<br />
<span onClick={() => this.handleSetUserId(items.id)}>
{items.name}
<br />
</span>
{items.id === this.state.userId && (
<p>
<br />
<div className="user-details">{items.phone}</div>
<div className="user-details">{items.company.name}</div>
<div className="user-details">{items.username}</div>
<div className="user-details">{items.email}</div>
<div className="user-details">{items.website}</div>
</p>
)}
</span>
))}
</div>
<p className="para-text"> Data from API</p>
<h2>Find User By Username</h2>
<div className="input-box">
<select onChange={(event) => this.handleSelectUserName(event)}>
{this.state.orgList.map((items) => (
<option key={items.name} value={items.id}>
{items.username}
</option>
))}
</select>
{/* Auto select */}
<div className=" Show-User-Auto">
<div className="input-box">
<input
type="text"
placeholder=" Auto Select"
required="required"
onChange={(event) => this.handleInputChange(event)}
value={this.state.name}
/>
</div>
</div>
</div>
</div>
);
}
}
export default Users;
I have implemented the same in codesandbox you can use for ref

How to change input value when click on another input box in React

I am learning React. I am trying to select country value in input box and handlechange event will target the country code and value will select automatically. I did't add functions handlechange and handleClick here as they were not working. Also I am getting different input box for each field. How to get only one input box with dropdown list?
export default class Api extends Component {
constructor(props) {
super(props);
this.state = {
country: [],
countryCode:''
};
}
componentDidMount() {
fetch("https://restcountries.eu/rest/v2/all")
.then((res) => res.json())
.then((result) => {
console.log(result);
console.warn(result);
this.setState({ country: result });
console.log("i am in console");
});
}
this.handleClick = this.handleClick.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
render() {
return (
<div>
<h1 className="text-center"> Api</h1>
<h2> country details</h2>
<div>
{this.state.country.map((countries, i) => (
<div key={i}>
<label>Select Country</label>
<select>
<option onClick={this.handleClick}> {countries.name}</option>
</select>
<label>Country Code: </label><input type="text" className="m-5" value={countries.callingCodes} onChange={this.handleInputChange} />
</div>
))}
</div>
</div>
);
}
}
You can play around with my changes here.
import React, { Component } from "react";
export default class Api extends Component {
state = {
countries: [],
countryName: "",
countryCode: ""
};
handleCountryPick = (event) => {
event.preventDefault();
const country = this.state.countries.find(
(country) => country.name === event.target.value
);
this.setState({
countryCode: country.callingCodes,
countryName: country.name
});
};
async componentDidMount() {
const response = await fetch("https://restcountries.eu/rest/v2/all");
const countries = await response.json();
this.setState({ countries: countries });
}
render() {
return (
<div>
<h1 className="text-center">Api</h1>
<h2>country details</h2>
<CountrySelector
countries={this.state.countries}
countryName={this.state.countryName}
onCountryPickHandler={this.handleCountryPick}
/>
<CountryCodeInput countryCode={this.state.countryCode} />
</div>
);
}
}
const CountrySelector = ({ countryName, countries, onCountryPickHandler }) => {
const options = countries.map((country) => (
<option key={country.name} value={country.name}>
{country.name}
</option>
));
return (
<div>
<select value={countryName || "none"} onChange={onCountryPickHandler}>
{options}
<option value="none">None</option>
</select>
</div>
);
};
const CountryCodeInput = ({ countryCode }) => {
return (
<div>
<label>Country Code: </label>
<input type="text" value={countryCode} />
</div>
);
};
Here is what you need to learn to be able to implement from scratch:
data binding is one-way, from parent to child, that is why you need to keep your handler (callback) handleCountryPick in the parent that keeps the state countries, countryName, countryCode.
time when updates happen and which components know what at which moment.
Have a look at what's inside the Array.map()
(I renamed your state.country to state.countries as it is an array.)
handleCountrySelectChange(e) {
const selectedCountry = e.target.value
// ...
}
handleInputChange(e) {
const countryCode = e.target.value
// ...
}
return (
<div>
<h1 className="text-center">Api</h1>
<h2>country details</h2>
<div>
<div>
<label>Select Country</label>
<select onChange={this.handleCountrySelectChange}>
{this.state.countries.map(country => (
<option key={country.name} value={country.name}>
{country.name}
</option>
))}
</select>
<label>Country Code: </label>
<input type="text" className="m-5" onChange={this.handleInputChange} />
</div>
</div>
</div>
)
}

I need to pass a URL from a class component to another

here is my QR component :
import React, { Component } from "react";
class QR extends React.Component {
constructor(props) {
super(props);
}
render() {
const { catImageUrl } = this.props;
const qrUrl = `https://qrtag.net/api/qr_12.svg?url=${catImageUrl}`;
if (!catImageUrl) return <p>Oops, something went wrong!</p>;
return <img className="QR" src={qrUrl} alt="qrtag" />;
}
}
export default QR;
I need to pass the const qrURL to the next component Form.js to use it in ajax call to get its data and pass it to the next api request to send it to an email
class Form extends React.Component{
constructor(props) {
super(props);
this.state = {
name: '',
email: '',
message: '',
}
}
const{qrUrl}=this.props;
FetchQRURL = () => {
fetch(`${qrUrl}`)
.then((response) => response.json())
.then((data) => {
this.setState({
message: data,
});
})
.catch((error) => console.log(error));
};
handleSubmit(e){
e.preventDefault();
axios({
method: "POST",
url:"http://localhost:3002/send",
data: this.state
}).then((response)=>{
if (response.data.status === 'success'){
alert("Message Sent.");
this.resetForm()
}else if(response.data.status === 'fail'){
alert("Message failed to send.")
}
})
}
resetForm(){
this.setState({name: '', email: '', message: ''})
}
render() {
return(
<div className="App">
<form id="contact-form" onSubmit={this.handleSubmit.bind(this)} method="POST">
<div className="form-group">
<label htmlFor="name">Name</label>
<input type="text" className="form-control" id="name" value={this.state.name} onChange={this.onNameChange.bind(this)} />
</div>
<div className="form-group">
<label htmlFor="exampleInputEmail1">Email address</label>
<input type="email" className="form-control" id="email" aria-describedby="emailHelp" value={this.state.email} onChange={this.onEmailChange.bind(this)} />
</div>
<div className="form-group">
<label htmlFor="message">Message</label>
<textarea className="form-control" rows="5" id="message" value={this.state.message} onChange={this.onMessageChange.bind(this)} />
</div>
<button type="submit" className="btn btn-primary" onClick="">Submit</button>
</form>
</div>
);
}
onNameChange(event) {
this.setState({name: event.target.value})
}
onEmailChange(event) {
this.setState({email: event.target.value})
}
onMessageChange(event) {
this.setState({message: event.target.value})
}
}
export default Form;
you can see I tried to pass it as props but it doesn't work
here I tried to pass it as props(in my app.js)
import React, { Component } from "react";
import RandomCat from "./RandomCat.js";
import QR from "./QR.js";
import Form from "./form.js";
class BooksApp extends Component {
state = {
showCatImage: false,
showQrCode: false,
catImageUrl: "",
};
handleFetchRandomImage = () => {
fetch("https://aws.random.cat/meow")
.then((response) => response.json())
.then((data) => {
this.setState({
catImageUrl: data.file,
showCatImage: true,
});
})
.catch((error) => console.log(error));
};
handleShowQrCode = () => {
this.setState({ showQrCode: true });
};
render() {
const { showCatImage, showQrCode, catImageUrl,qrUrl } = this.state;
return (
<div className="app">
<div className="first">
{/* The time below shows cat image if showCatImage === true and returns nothing if false */}
{showCatImage && <RandomCat catImageUrl={catImageUrl} />}
<button className="catButton" onClick={this.handleFetchRandomImage}>
Generate Cat
</button>
</div>
<div className="second">
{showQrCode && <QR catImageUrl={catImageUrl} qrUrl={qrUrl}/>}
<button className="QRButton" onClick={this.handleShowQrCode}>
Geanerate QR
</button>
</div>
<div>
<Form qrUrl={qrUrl}/>
</div>
</div>
);
}
}
export default BooksApp;
any idea how can I pass it to the Form.js?
You have to pull the constant qrUrl to the parent component which is BooksApp in your case.
Set it to the state and pass it down as props.
state = {
showCatImage: false,
showQrCode: false,
catImageUrl: "",
qrUrl: ""
};
handleFetchRandomImage = () => {
fetch("https://aws.random.cat/meow")
.then((response) => response.json())
.then((data) => {
this.setState({
catImageUrl: data.file,
showCatImage: true,
qrUrl: `https://qrtag.net/api/qr_12.svg?url=${data.file}` // Set it here
});
})
.catch((error) => console.log(error));
};
handleShowQrCode = () => {
this.setState({ showQrCode: true });
};
render() {
const { showCatImage, showQrCode, catImageUrl, qrUrl } = this.state;
return (
<div className="app">
<div className="first">
{/* The time below shows cat image if showCatImage === true and returns nothing if false */}
{showCatImage && <RandomCat catImageUrl={catImageUrl} />}
<button className="catButton" onClick={this.handleFetchRandomImage}>
Generate Cat
</button>
</div>
<div className="second">
{showQrCode && <QR catImageUrl={catImageUrl} qrUrl={qrUrl}/>}
<button className="QRButton" onClick={this.handleShowQrCode}>
Geanerate QR
</button>
</div>
<div>
<Form qrUrl={qrUrl}/>
</div>
</div>
);
}
}
export default BooksApp;
The just use it with this.props.qrUrl in your other components.

How to design State for Multiple objects using react-redux?

I need to create multiple medicine objects here. I just want to change state into array of objects. How to do that effectively? Also, want to implement controlled component for multiple medicine objects form.
Here's my component for a single medicine object:
export class MedicineForm extends Component {
state = {
medicine_name: "",
details: ""
}
static propTypes = {
postMedicine: PropTypes.func.isRequired
}
onChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
onSubmit = e => {
e.preventDefault()
const { medicine_name, details } = this.state
const medicine = { medicine_name, details }
this.props.postMedicine(medicine)
// Following code works as desired. Need to change state in this JSON Array of objects.
// this.props.postMedicine([
// {
// "id": 14,
// "medicine_name": "many5",
// "details": "sdknas"
// },
// {
// "id": 15,
// "medicine_name": "many6",
// "details": "sdknas"
// }
// ])
}
render() {
const { medicine_name, details } = this.state
return (
<Fragment>
<h1>Add Medicine</h1>
<form className="card card-body" onSubmit={this.onSubmit}>
<div className="form-row">
<div className="form-group col-md-3">
<label htmlFor="medicine_name">Medicine Name</label>
<input type="text" className="form-control" name="medicine_name" id="medicine_name" placeholder="Medicine Name" value={medicine_name} onChange={this.onChange} />
</div>
<div className="form-group col-md-3">
<label htmlFor="details">Details</label>
<input type="text" className="form-control" name="details" id="details" placeholder="Details" value={details} onChange={this.onChange} />
</div>
<div className="form-group mx-auto mt-3">
<button type="submit" className="btn btn-primary btn-lg">
Submit
</button>
</div>
</div>
</form>
</Fragment>
)
}
}
In actions, I have added following postMedicine method:
export const postMedicine = (medicine) => dispatch => {
axios.post('./api/medicine/', medicine)
.then(res => {
dispatch({
type: POST_MEDICINE,
payload: res.data
})
})
.catch(err => console.log(err))
}
//this is one row, add multiple rows as needed
state = {
medicines: [{medicine_name: "",
details: ""
}]
}
//other code
onChange = (e, i) => {
const newMedicines = this.state.medicines;
newMedicines[i] = {[e.target.name]: e.target.value, ...newMedicines[i]}
this.setState({medicines: newMedicines})
}
onSubmit = e => {
e.preventDefault()
const { medicine_name, details } = this.state
const medicine = { medicine_name, details }
this.props.postMedicine(medicine)
// Following code works as desired. Need to change state in this JSON Array of objects.
// this.props.postMedicine(this.state.medicines)
}
<form className="card card-body" onSubmit={this.onSubmit}>
{this.state.medicines.map((m, i) => (<div className="form-row">
<div className="form-group col-md-3">
<label htmlFor="medicine_name">Medicine Name</label>
<input type="text" className="form-control" name="medicine_name" id="medicine_name" placeholder="Medicine Name" value={m.medicine_name} onChange={(e) => this.onChange(e, i)} />
</div>
<div className="form-group col-md-3">
<label htmlFor="details">Details</label>
<input type="text" className="form-control" name="details" id="details" placeholder="Details" value={m.details} onChange={(e) => this.onChange(e, i)} />
</div>
<div className="form-group mx-auto mt-3">
<button type="submit" className="btn btn-primary btn-lg">
Submit
</button>
</div>
</div>))}
</form>
Form component has two parameter (aka props).
first one is item, wich determines how many form do you need.
1 form means you have group of two inputs [medicine_name,details]
2 = 4 input (2 group)
...etc
and second props is function named formHandler.
wich lifting data from child
export class MedicineForm extends Component {
state = {
medicine: [],
};
static propTypes = {
postMedicine: PropTypes.func.isRequired,
};
formHandler = (value) => {
this.setState({ medicine: value });
};
onSubmit = (e) => {
e.preventDefault();
this.props.postMedicine(this.medicine);
};
render() {
return (
<>
<h1>Add Medicine</h1>
{JSON.stringify(this.state.medicine)}
<form className="card card-body" onSubmit={this.onSubmit}>
<Form item="4" formHandler={this.formHandler} />
<div className="form-group mx-auto mt-3">
<button type="submit" className="btn btn-primary btn-lg">
Submit
</button>
</div>
</form>
</>
);
}
}
Form Component
class Form extends Component {
constructor(props) {
super(props);
}
state = {
medicine: [...Array(+this.props.item)].map((_, idx) => ({
id: idx + 1,
medicine_name: "",
details: "",
})),
};
static propTypes = {
item: PropTypes.string,
formHandler: PropTypes.func,
};
onChange = ({ target: { id, name, value } }) => {
this.setState((prevState) => {
const medicine = prevState.medicine.map((item) =>
item.id === Number(id) ? { ...item, [name]: value } : item
);
this.props.formHandler(
medicine.filter((item) => item["medicine_name"] || item["details"])
);
return {
medicine,
};
});
};
render() {
return (
<div className="form-row">
{this.state.medicine.map((item, id) => (
<div key={item.id}>
<div className="form-group col-md-3">
<label htmlFor="medicine_name">Medicine Name</label>
<input
type="text"
className="form-control"
name="medicine_name"
id={item.id}
value={item.medicine_name}
placeholder="Medicine Name"
onChange={this.onChange}
/>
</div>
<div className="form-group col-md-3">
<label htmlFor="details">Details</label>
<input
type="text"
className="form-control"
name="details"
id={item.id}
value={item.details}
placeholder="Details"
onChange={this.onChange}
/>
</div>
</div>
))}
</div>
);
}
}
there is check if object has some value then lifting data.
you can change logic optional
medicine.filter((item) => item["medicine_name"] || item["details"])
You could do something like this in redux store:
[
{ id: 1, medicineName: '', details: '' },
{ id: 2, medicineName: '', details: '' },
...
]
And to make your input fields controlled just handle the state in the component.

Resources