How to a make dynamic form in React - reactjs

I'm learning React. My target is to make this object:
"Phones": [
{
"Type": "Mobile",
"Phone_Number": "6546543"
},
{
"Type": "Home",
"Phone_Number": "6546543"
}
]
I did some research and I followed this YouTube video ReactJS - Dynamically Adding Multiple Input Fields.
These values I'll fetch from a form. Th user can keep on adding as many numbers as he wants. My code is:
render() {
return (
<div>
<h1>The Form</h1>
<label>Contact</label>
{this.state.phones.map((phone, index) => {
return (
<div key={index}>
<input onChange={(e) => this.handleChange(e)} value={phone} />
<select>
{this.phoneTypes.map((phoneType, index) => {
return (
<option key={index} value={phoneType}>
{phoneType}
</option>
);
})}
</select>
<button onClick={(e) => this.handleRemove(e)}>Remove </button>
</div>
);
})}
<hr />
<button onClick={(e) => this.addContact(e)}>Add contact</button>
<hr />
<button onClick={(e) => this.handleSubmit(e)}>Submit</button>
</div>
);
}
I'm not pasting the entire code here as I've already created a stackblitz. My code is not working as expected. Please pitch in.

You need to pass index in the handleChange function for input field. I guess you missed that part from video.This will make the input field editable.
<input
onChange={(e) => this.handleChange(e, index)}
value={phone}
/>
For the second part to get the expected object array I have updated some code, please check:
import React, { Component } from 'react';
class Questionnaire extends Component {
state = {
phones: [{ type: '', number: '' }],
};
phoneTypes = ['Mobile', 'Home', 'Office'];
addContact() {
this.setState({ phones: [...this.state.phones, { type: '', number: '' }] });
}
handleChange(e, index) {
this.state.phones[index]['number'] = e.target.value;
this.setState({ phones: this.state.phones });
}
handleRemove(index) {
this.state.phones.splice(index, 1);
console.log(this.state.phones, '$$$');
this.setState({ phones: this.state.phones });
}
handleSubmit(e) {
console.log(this.state, '$$$');
}
render() {
return (
<div>
<h1>The Form</h1>
<label>Contact</label>
{this.state.phones.map((phone, index) => {
return (
<div key={index}>
<input
onChange={(e) => this.handleChange(e, index)}
value={phone.number}
name="number"
/>
<select>
{this.phoneTypes.map((phoneType, index) => {
return (
<option key={index} value={phoneType} name="type">
{phoneType}
</option>
);
})}
</select>
<button onClick={(e) => this.handleRemove(e)}>Remove </button>
</div>
);
})}
<hr />
<button onClick={(e) => this.addContact(e)}>Add contact</button>
<hr />
<button onClick={(e) => this.handleSubmit(e)}>Submit</button>
</div>
);
}
}
export default Questionnaire;

You can use input objects like formik
import { useState } from 'react';
const Component = () => {
const [inputs, setInputs] = useState({ // Initial inputs
email: "",
phoneNumber: "",
});
const handleSubmit = (e) => {
e.preventDefault();
// Do
};
const handleChange = (e) => {
setInputs({...inputs, [e.target.id]: e.target.value});
};
const addInput = () => {
const name = window.prompt("Type a name", "");
setInputs({...inputs, [name]: ""});
};
return (
<form onSubmit={handleSubmit}>
{object.keys(inputs).map((name, i) => {
<input type="text" onChange={handleChange} name={name} value={inputs[name]} />
})}
<button
type="button"
onClick={addInput}
>
Add Input
</button>
</form>
);
}

Related

NextJS - Passing data via property to component for editing

I have a page where it will list all books to the admin. And if admin clicks on the EDIT button of any particular book, my app will display a custom modal where it would display the input boxes with the values in it and lets the admin to edit it.
Minimal example of my component:
import styles from '../../styles/EditPopup.module.css'
export default function EditPopup({ show, setPopupVisibility, item_data }){
const onSaveEdit = () => {
//TODO
}
return (
<div className={ styles.popup } style={ show ? {} : { display: 'none' } }>
<input type="text" defaultValue={ item_data.title } />
<input type="text" defaultValue={ item_data.author } />
<textarea defaultValue={ item_data.desc}></textarea>
<button onClick={ () => onSaveEdit() } >SAVE</button>
<button onClick={ () => setPopupVisibility( false ) }>CANCEL</button>
</div>
)
}
Minimal example on my page where it list all books with the edit button:
const [selected_book, setSelectedBook] = useState({ id: '', title: '', author: '', desc: '' })
const [show_edit_popup, setShowEditPopup] = useState(false)
return (
<EditPopup
show={ show_edit_popup }
setPopupVisibility={ (visibility) => setShowEditPopup( visibility ) }
item_data={ selected_book }
/>
<div>
{ all_books.map( (item, i) => (
<div key={ item.id }>
Title: {item.title}
Author: {item.author}
Desc: {item.desc}
<a href="#"
onClick={ (e) => {
e.preventDefault()
setSelectedBook( prev => ({ ...prev, id: item.id, title: item.title, author: item.author, desc: item.desc}) )
setShowEditPopup( true )
} }
>EDIT</a>
</div>
))}
</div>
)
The problem is, it is not working properly in the popup. The for desc will always be empty in the popup. And if I type some new value in any of the inputs and I close the popup and clicks the EDIT button on any other item, the value I entered previously in the inputs will still be there. And secondly am being confused here regarding how to get the new user input. Should I go with the useRef or useState with onChange
I know this is something simple for all of you, but am overthinking I guess.
You are not handling states correctly, So go for it with useState and onChange. For your inputs set an onChange as a callback function that comes from the parent component, pass the event.target.value through it, and set them in the state initiated in the parent.
also, minimize your state's declaration by batching them in one state.
the code could be like this:
import styles from '../../styles/EditPopup.module.css'
export default function EditPopup({ data, handleClosePopUp, onSaveEdit, handleChangeInputs }){
return (
<div className={ styles.popup } style={ data.show ? {} : { display: 'none' } }>
<input type="text" onChange={e => handleChangeInputs(e.target.value, 'title')} value={ data.title } />
<input type="text" onChange={e => handleChangeInputs(e.target.value, 'author')} value={ data.author } />
<textarea onChange={e => handleChangeInputs(e.target.value, 'desc')} value={ data.desc}></textarea>
<button onClick={onSaveEdit} >SAVE</button>
<button onClick={handleClosePopUp}>CANCEL</button>
</div>
)
}
and your parent
const initialData = { show: false, id: '', title: '', author: '', desc: '' }
const [popUpData, setPopUpData] = useState({...initialData})
const handleChangeInputs = (text, type) => {
setPopUpData(data => {...data, data[type] = text})
}
const onSaveEdit = () => {
//do your API call or whatever it updates all_books
}
return (
<EditPopup
handleChangeInputs={handleChangeInputs}
onSaveEdit={onSaveEdit}
handleClosePopUp={ () => setPopUpData({...initialData}) }
data={ popUpData }
/>
<div>
{ all_books.map(item) => (
<div key={ item.id }>
Title: {item.title}
Author: {item.author}
Desc: {item.desc}
<a href="#"
onClick={ (e) => {
e.preventDefault()
setPopUpData({ show: true, ...item})
} }
>EDIT</a>
</div>
))}
</div>
)

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>
)
}

How to maintain controlled input which are added dynamically on user click with React Hooks

I've got a form component, where you can add multiple inputs on click. I am using useState to store data from all of the inputs, but the problem is I am receiving a message that my inputs are uncontrolled
State data structure
const [formData, setFormData] = useState({
title: '',
titleNum: '',
meta: [
{
content: '',
contentNum: '',
details: '',
tags: ''
}
]
});
The idea is to dynamically add multiple objects via form to meta array
onChange events added to inputs
const { title, titleNum, meta } = formData;
const handleMainChange = e => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubChange = (e, index) => {
const { name, value } = e.target;
let metaContent = [...meta];
metaContent[index] = { ...metaContent[index], [name]: value };
setFormData({ ...formData, meta: metaContent });
};
Adding and removing inputs
const handleAddInput = () => {
setFormData(prevState => ({
meta: [
...prevState.meta,
{ content: '', contentNum: '', details: '', tags: '' }
]
}));
};
const handleRemove = index => {
let metaContent = [...meta];
metaContent.splice(index, 1);
setFormData({ meta: metaContent });
};
Submit and looping through all META
const onFormSubmit = e => {
e.preventDefault();
createAccount(formData, history);
};
const renderList = meta.map((item, index) => {
console.log(item);
return (
<AddMeta
key={index}
index={index}
meta={item}
handleChange={e => handleSubChange(e, index)}
handleRemove={() => handleRemove(index)}
/>
);
});
AddMeta Component
const AddMeta = ({ index, meta, handleChange, handleRemove }) => {
return (
<Fragment>
<div className='row valign-wrapper'>
<div className='col s3'>
<h5 className='indigo-text text-lighten-1'>Add Meta - {index + 1}</h5>
</div>
<div className='col s9'>
<button onClick={() => handleRemove(index)} className='btn red'>
Remove Meta Content
</button>
</div>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Content Text'
name='content'
value={meta.content || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Content Number'
name='contentNum'
value={meta.contentNum || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
<div className='form-group'>
<textarea
className='materialize-textarea'
type='text'
placeholder='Details'
name='details'
value={meta.details || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Tags'
name='tags'
value={meta.tags || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
</Fragment>
);
};
I appreciate any attempt to solve this problem.
Thanks!
Try removing the || '' on your inputs value. Their value should always be tied to your state (which should be '' when you add a new meta).

Resources