How to write a generic method to handle multiple state changes in React - javascript

I'm building an exercise tracker app in React.
Right now, I'm building the CreateExercise component to submit a form, so I need to update the states of each value. In order to do so, I created methods to handle those changes (onChangeUsername, onChangeDescription, onChangeDuration etc...) but I don't really like to repeat methods like this.
How to write a more generic method to handle this task ?
class CreateExercise extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
description: '',
duration: 0,
date: new Date(),
users: []
}
}
onChangeUsername = (e) => {
this.setState({
username: e.target.value
});
}
onChangeDescription = (e) => {
this.setState({
description: e.target.value
});
}
onChangeDuration = (e) => {
this.setState({
duration: e.target.value
});
}
onChangeDate = (date) => {
this.setState({
date: date
});
}
onSubmit = (e) => {
e.preventDefault();
const exercise = {
username: this.state.username,
description: this.state.description,
duration: this.state.duration,
date: this.state.date
}
console.log(exercise);
window.location = '/';
}
render() {
return(
<div>
<h3>Create New Exercise Log</h3>
<form onSubmit={ this.onSubmit }>
<div className='form-group'>
<label>Username:</label>
<select
ref='userInput'
required
className='form-control'
value={ this.state.username }
onChange={ this.onChangeUsername }
>
{ this.state.users.map((user) => (
<option key={user} value={user}>{user}</option>
))
}
</select>
</div>
<div className='form-group'>
<label>Description:</label>
<input
type='text'
required
className='form-control'
value={ this.state.description }
onChange={ this.onChangeDescription}
/>
</div>
<div className='form-group'>
<label>Duration:</label>
<input
type='text'
className='form-control'
value={ this.state.duration }
onChange={ this.onChangeDuration }
/>
</div>
<div className='form-group'>
<label>Date:</label>
<div>
<DatePicker
selected={ this.state.date }
onChange={ this.onChangeDate }
/>
</div>
</div>
<div className='form-groupe'>
<input
type='submit'
value='Create Exercise Log'
className='btn btn-primary'
/>
</div>
</form>
</div>
);
}
}
export default CreateExercise;

Using partial application, create a function in your component that takes a field name, and returns a function that sets the state:
onChangeValue = field => e => {
this.setState({
[field]: e.target.value
});
};
Usage:
onChangeUsername = onChangeValue('username');
onChangeDescription = onChangeValue('description');
onChangeDuration = onChangeValue('duration');
You extend the idea further to support the onChangeDate as well:
onChangeValue = (field, valueTransformer = e => e.target.value) => e => {
this.setState({
[field]: valueTransformer(e.target.value)
});
};
This doesn't change the other on functions, since the default is to get e.target.value. To use onChangeDate we can now change the valueTransformer:
onChangeDate = onChangeValue('date', v => v);

You can define name for the HTML element, and use that to set value:
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
corresponding JSX element:
<input
type="text"
name="description"
required
className="form-control"
value={this.state.description}
onChange={this.onChange}
/>

Related

creating unique id into object without UUID in React

I am trying to test my understanding of React but hit a mental block. Would it be possible to create a unique key / id within my <FormInput> component rather than creating the unique ID inside the <MainUI> component once the newly created item is passed up into <MainUI>? I find that weird and definitely feel like there is a way better method of doing this.
const MainUI = props => {
const receiveNewItem = enteredNewItem => {
const newItem = {
...enteredNewItem,
id: Math.random().toString()
}
props.addNewItem(newItem)
}
console.log(props.data)
return(
<div>
<FormInput receiveNewItem={receiveNewItem}/>
{/* <MaxAmount data={props.data} /> */}
<DisplayItems data={props.data} key={props.data.id} />
</div>
)
}
export default MainUI;
import { useState } from 'react'
import Card from './Card'
const FormInput = props => {
const [userInput, setUserInput] = useState({
name: '',
amount: '',
date: ''
})
const nameInputHandler = e => {
setUserInput( prevObj => {
return {...prevObj, name: e.target.value}
})
}
const amountInputHandler = e => {
setUserInput( prevObj => {
return {...prevObj, amount: e.target.value}
})
}
const dateInputHandler = e => {
setUserInput( prevObj => {
return {...prevObj, date: e.target.value}
})
}
const submitHandler = e => {
e.preventDefault()
props.receiveNewItem(userInput)
setUserInput( prevObj => { return {...prevObj, name: ''}})
}
return (
<Card>
<form onSubmit={submitHandler}>
<input
type="text"
placeholder="Name Input"
value={userInput.name}
onChange={nameInputHandler} />
<input
type="number"
placeholder="Amount Input"
value={userInput.amount}
onChange={amountInputHandler} />
<input
type="date"
placeholder="Date Input"
value={userInput.date}
onChange={dateInputHandler} />
<button>Add Item</button>
</form>
</Card>
)
}
export default FormInput;

Add multiple input field dynamically in react

I have is a div that contains three input elements and a remove button. what is required is when the user clicks on the add button it will add this div dynamically and when the user clicks on the remove button it will remove this div. I was able to add one input element (without div container) dynamically with the following method.
create an array in the state variable.
assign a name to the dynamic input field with the help of array indexing like name0, name1
How can I do with these many input fields? The problem grows further when I create this whole div as a separate component. I am using a class-based component.
handleChange=(event) =>
{
this.setState({[event.target.name]:event.target.values});
}
render()
{
return(
<div className="row">
<button type="button" onClick={this.addElement}>Add</button>
<div className="col-md-12 form-group">
<input type="text" className="form-control" name="name" value={this.state.name} onChange={this.handleChange} />
<input type="text" className="form-control" name="email" value={this.state.email} onChange={this.handleChange} />
<input type="text" className="form-control" name="phone" value={this.state.phone} onChange={this.state.phone} />
<button type="button" onClick={this.removeElement}>Remove</button>
</div>
</div>
)
}
I would approach this from a configuration angle as it's a little more scalable. If you want to eventually change across to something like Formik or React Form, it makes the move a little easier.
Have an array of objects that you want to turn into input fields. Your main component should maintain state whether the <Form /> component is showing, and if it's visible pass in the config and any relevant handlers.
Your form component should maintain state for the inputs, and when you submit it, passes up the completed state to the parent.
const { Component } = React;
class Example extends Component {
constructor(props) {
super();
// The only state in the main component
// is whether the form is visible or not
this.state = { visible: false };
}
addForm = () => {
this.setState({ visible: true });
}
removeForm = () => {
this.setState({ visible: false });
}
handleSubmit = (form) => {
console.log(form);
}
render() {
const { visible } = this.state;
const { config } = this.props;
return (
<div>
<button
type="button"
onClick={this.addForm}
>Add form
</button>
{visible && (
<Form
config={config}
handleSubmit={this.handleSubmit}
handleClose={this.removeForm}
/>
)}
</div>
);
}
};
class Form extends Component {
constructor(props) {
super();
this.state = props.config.reduce((acc, c) => {
return { ...acc, [c.name]: '' };
}, {});
}
handleChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit = () => {
this.props.handleSubmit(this.state);
}
render() {
const { name, email, phone } = this.state;
const { handleClose, config } = this.props;
return (
<div onChange={this.handleChange}>
{config.map(input => {
const { id, name, type, required } = input;
return (
<div>
<label>{name}</label>
<input key={id} name={name} type={type} required={required} />
</div>
)
})}
<button type="button" onClick={this.handleSubmit}>Submit form</button>
<button type="button" onClick={handleClose}>Remove form</button>
</div>
);
}
}
const config = [
{ id: 1, name: 'name', type: 'text', required: true },
{ id: 2, name: 'email', type: 'email', required: true },
{ id: 3, name: 'phone', type: 'phone', required: true }
];
ReactDOM.render(
<Example config={config} />,
document.getElementById('react')
);
input { display: block; }
label { text-transform: capitalize; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I hope this would be help for your question.
I made a child component which have three input tags.
// parent component
import React, { Component } from "react";
import TextField from "./TextField";
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: [
{
key: Date.now(),
name: "",
email: "",
phone: ""
}
]
};
}
onChange = (inputUser) => {
this.setState((prevState) => {
const newUsers = prevState.users.map((element) => {
if (element.key === inputUser.key) return inputUser;
return element;
});
return { users: newUsers };
});
};
addElement = () => {
const { name, email, phone } = this.state;
this.setState((prevState) => ({
users: prevState.users.concat({
key: Date.now(),
name,
email,
phone
})
}));
};
removeElement = (id) => {
this.setState((prevState) => ({
users: prevState.users.filter((user) => user.key !== id)
}));
};
render() {
const { users } = this.state;
return (
<div className="row">
<button type="button" onClick={this.addElement}>
Add
</button>
<div className="col-md-12 form-group">
{users.map((user) => (
<React.Fragment key={user.key}>
<TextField
value={user}
onChange={(inputUser) => this.onChange(inputUser)}
/>
<button
type="button"
onClick={() => this.removeElement(user.key)}
disabled={users.length <= 1}
>
Remove
</button>
</React.Fragment>
))}
</div>
</div>
);
}
}
export default App;
// child component
import { Component } from "react";
class TextField extends Component {
handleChange = (ev) => {
const { name, value } = ev.target;
this.props.onChange({
...this.props.value,
[name]: value
});
};
render() {
const { value: user } = this.props;
return (
<>
<input
className="form-control"
name="name"
value={user.name}
onChange={this.handleChange}
placeholder="name"
type="text"
/>
<input
className="form-control"
name="email"
value={user.email}
onChange={this.handleChange}
placeholder="email"
type="text"
/>
<input
className="form-control"
name="phone"
value={user.phone}
onChange={this.handleChange}
placeholder="phone"
type="text"
/>
</>
);
}
}
export default TextField;
You can also check the code in codesandbox link below.
https://codesandbox.io/s/suspicious-heisenberg-xzchm
It was a little difficult to write down every detail of how to generate what you want. So I find it much easier to ready a stackblitz link for you to see how is it going to do this and the link is ready below:
generating a dynamic div adding and removing by handling inputs state value
const { Component } = React;
class Example extends Component {
constructor(props) {
super();
// The only state in the main component
// is whether the form is visible or not
this.state = { visible: false };
}
addForm = () => {
this.setState({ visible: true });
}
removeForm = () => {
this.setState({ visible: false });
}
handleSubmit = (form) => {
console.log(form);
}
render() {
const { visible } = this.state;
const { config } = this.props;
return (
<div>
<button
type="button"
onClick={this.addForm}
>Add form
</button>
{visible && (
<Form
config={config}
handleSubmit={this.handleSubmit}
handleClose={this.removeForm}
/>
)}
</div>
);
}
};
class Form extends Component {
constructor(props) {
super();
this.state = props.config.reduce((acc, c) => {
return { ...acc, [c.name]: '' };
}, {});
}
handleChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit = () => {
this.props.handleSubmit(this.state);
}
render() {
const { name, email, phone } = this.state;
const { handleClose, config } = this.props;
return (
<div onChange={this.handleChange}>
{config.map(input => {
const { id, name, type, required } = input;
return (
<div>
<label>{name}</label>
<input key={id} name={name} type={type} required={required} />
</div>
)
})}
<button type="button" onClick={this.handleSubmit}>Submit form</button>
<button type="button" onClick={handleClose}>Remove form</button>
</div>
);
}
}
const config = [
{ id: 1, name: 'name', type: 'text', required: true },
{ id: 2, name: 'email', type: 'email', required: true },
{ id: 3, name: 'phone', type: 'phone', required: true }
];
ReactDOM.render(
<Example config={config} />,
document.getElementById('react')
);
input { display: block; }
label { text-transform: capitalize; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Form update in React : input previous data is erased if the input is left empty when submitted

So, I have a data persistence issue with my form inputs.
If I modify all inputs everything is fine.
But if an input is left empty, its previous data is erased when I submit. I need suggestions for my handleChange to keep data even when an input is not modified.
I tried this but it failed too :
handleChange = e => {
e.persist();
this.setState(prevState => ({
product: { ...prevState.product, [e.target.name]: e.target.value }
}))
}
Here is my EditForm, thanks for your help.
EditForm.js
export default class EditForm extends Component {
constructor(props) {
super(props);
this.state = { product: [] };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
componentDidMount = () => {
axios
.get(`/products/edit-form/${this.props.match.params.id}`)
.then(response => {
console.log(response.data.products);
this.setState({
product: response.data.products
})
});
};
handleChange(e) {
console.log(e.target.name);
this.setState({[e.target.name]: e.target.value})
}
handleSubmit(e) {
const data = {
id: this.props.match.params.id,
reference: this.state.reference,
designation: this.state.designation
}
e.preventDefault();
console.log(data);
axios
.post(`/products/${data.id}`, data )
.then(res => console.log(res))
.catch(err => console.log(err));
};
renderForm() {
return this.state.product.map((product, index) => {
const { id,reference,designation } = product
return(
<>
<Form className="post" onSubmit={this.handleSubmit}>
<Form.Row>
<Form.Group as={Col} controlId="formGridReference">
<Form.Label>Reference</Form.Label>
<Form.Control type="text" value={this.state.product.reference}
onChange={this.handleChange} name="reference" placeholder={reference}/>
</Form.Group>
<Form.Group as={Col} controlId="formGridDesignation">
<Form.Label>Designation</Form.Label>
<Form.Control type="text" value={this.state.product.designation}
onChange={this.handleChange} name="designation" placeholder={designation}/>
</Form.Group>
</Form.Row>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</>
);
})
}
render() {
return (
<div>
<h1>Formulaire de modification</h1>
{this.renderForm()}
</div>
);
}
}```
In your EditForm.js, inside handleChange(), you have to maintain your previous state. You can do so by creating a copy of your state. Then use the copy to update your state property values and in the end, use this.setState();
Example:
const state = this.state;
state['Your property'] = 'value';
...
...
this.setState(state);

using component state in another component react js

Im working on a little web app, and im stuck on getting the answers data from answer class that i created.
to sum the project, i have a class createQuiz, where i handle the input for question data, and i have an answerOptions state array to collect all the answers, but i created another component answer to have the states of the answers and when i modify them, the answeroptions dont update.
Here is the code for the answer class :
import React, {Component} from 'react';
import createQuiz from './CreateQuiz'
export default class Answer extends Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.onChangeAnswerValidity = this.onChangeAnswerValidity.bind(this);
this.ChangeValidityState = this.ChangeValidityState.bind(this);
this.state = {
answerText : '',
answerValidity : false
}
}
getCurrentState(){
return (this.state)
}
handleInputChange(event) {
this.setState({
answerText : event.target.value
})
}
onChangeAnswerValidity(event){
this.setState({
answerValidity : !event.target.value
})
}
ChangeValidityState(event){
this.setState({
answerValidity : !event.target.value
})
}
render() {
return (
<div>
<input type="text"
className="form-control"
value={this.state.answerText}
onChange={this.handleInputChange}
/>
<input type="radio"
className="form-control"
value={this.state.answerValidity}
checked={this.state.answerValidity}
onChange={this.onChangeAnswerValidity}
/>
</div>
)
}
}
and here is the code for the create quiz class :
import React, {Component} from 'react';
import axios from 'axios';
import Answer from './Answer';
export default class CreateQuiz extends Component {
constructor(props) {
super(props);
this.onChangeQuestion = this.onChangeQuestion.bind(this);
this.onChangeQuestionTitle = this.onChangeQuestionTitle.bind(this);
this.onChangeAnswerOptions = this.onChangeAnswerOptions.bind(this);
this.onAddItem = this.onAddItem.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.onChangeQuestionAutor = this.onChangeQuestionAutor.bind(this);
this.onChangeValue = this.onChangeValue.bind(this);
this.onChangeAnswerValidity = this.onChangeAnswerValidity.bind(this);
this.onSubmit = this.onSubmit.bind(this);
// this.GenerateAnswers = this.GenerateAnswers.bind(this);
this.state = {
questionTitle: '',
questionAutor: '',
question: '',
answers : [],
answerOptions: [],
}
}
onChangeValue = event => {
this.setState({ currentAnswerValue: event.target.value });
};
onAddItem = () => {
// not allowed AND not working
this.setState(state => {
const newAnswer = {
answerText : '',
answerValidity : false
};
const list = this.state.answerOptions.push(newAnswer);
return {
list,
};
});
};
handleInputChange(event) {
const newIds = this.state.answerOptions.slice() //copy the array
const index = this.state.answerOptions.findIndex((el) => (el.id === event.params.id))
newIds[index].answerText = event.target.value //execute the manipulations
this.setState({answerOptions: newIds}) //set the new state
}
onChangeAnswerValidity(event){
const newIds = this.state.answerOptions.slice() //copy the array
const index = this.state.answerOptions.findIndex((el) => el.answerText === event.target.value)
newIds[index].answerValidity = event.target.value //execute the manipulations
this.setState({answerOptions: newIds}) //set the new state
}
onChangeQuestionAutor(e){
this.setState({
questionAutor: e.target.value
});
}
onChangeQuestionTitle(e) {
this.setState({
questionTitle: e.target.value
});
}
onChangeQuestion(e) {
this.setState({
question: e.target.value
});
}
onChangeAnswerOptions(e) {
this.setState({
answerOptions: e.target.value
});
}
onSubmit(e) {
e.preventDefault();
console.log(`Form submitted:`);
console.log(`questionTitle: ${this.state.questionTitle}`);
console.log(`questionAutor: ${this.state.questionAutor}`);
console.log(`question: ${this.state.question}`);
// this.GenerateAnswers();
console.log(`answerOptions: ${this.state.answers}`);
const newQuiz = {
questionTitle: this.state.questionTitle,
questionAutor: this.state.questionAutor,
question: this.state.question,
answerOptions: this.state.answers,
}
axios.post('http://localhost:4000/quizzes/add', newQuiz)
.then(res => console.log(res.data));
this.setState({
questionTitle: '',
questionAutor: '',
question: '',
answers : [],
answerOptions: []
})
}
answerList(){
return this.state.answerOptions.map(function(currentAnswer, index) {
return <Answer answer = {currentAnswer} key = {index}/>;
});
}
// GenerateAnswers(){
// const newAnswers = this.state.answers
// this.state.answerOptions.map(function(currentAnswer, index){
// const newAnswer = this.state.answerOptions[index].getCurrentState()
// newAnswers.push(newAnswer)
// });
// this.setState({answers: newAnswers});
// }
render() {
return (
<div style={{marginTop: 20}}>
<h3>Create new question</h3>
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label>Question Title: </label>
<input type="text"
className="form-control"
value={this.state.questionTitle}
onChange={this.onChangeQuestionTitle}
/>
</div>
<div className="form-group">
<label>Question Autor: </label>
<input type="text"
className="form-control"
value={this.state.questionAutor}
onChange={this.onChangeQuestionAutor}
/>
</div>
<div className="form-group">
<label>Question: </label>
<input type="text"
className="form-control"
value={this.state.question}
onChange={this.onChangeQuestion}
/>
</div>
<div>
<ul>
{this.answerList()}
</ul>
</div>
<div className="form-group">
<button type="button" onClick = {this.onAddItem} className="btn btn-primary"> Add answer </button>
</div>
<div className="form-group">
<input type="submit" value="submit Question" className="btn btn-primary" />
</div>
</form>
</div>
)
}
}
when i send the data to the database i always have in the answer fields empty string and false no update has been done.
thanks a lot,
Boubker ELAMRI
You’re modifying the list, so react doesn’t know it’s changed. You need to create a new array first, before setting the state
const list = Array.from(this.state.answerOptions)
list.push(newAnswer)

How to set my value in input value react js

I was trying to set my value in input value but all the time I was getting undefined in the console and I wanted to set values from API in value but I could not so how to set values in input value and i also tried to remove ref and see but still value input shows undefined when I set value={2} in input.
And here it is:
this.state = {
// movie: [],
user: this.props.value,
text: "",
errors: []
};
async componentDidMount() {
try {
const res = await fetch(
`https://softbike.dev.myddp.eu/api/1/deliveries/user1/`
);
const movie = await res.json();
console.log(movie);
this.setState({
movie: movie.pk
});
} catch (e) {
console.log(e);
}
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
console.log(this.state);
}
Here is input code:
<div className="row">
<div className="col-sm-12">
<label id="p"> UZYTKOWNIK</label>
<input
type="text"
ref="user"
className="form-control"
name="user"
value={movie.id}
onChange={this.handleChange}
/>
<p>g {this.state.value}</p>
</div>
Please help me how to resolve this.
<div className="col-sm-12">
<label id="p"> UZYTKOWNIK</label>
<input
type="text"
ref="user"
className="form-control"
name="user"
value={this.state.movie && this.state.movie.id}
onChange={this.handleChange}
/>
</div>
Try this:
this.state = {
movie: {},
user: this.props.value,
text: "",
errors: []
};
componentDidMount() {
const fetchMovie = async () => {
try {
const res = await fetch(`https://softbike.dev.myddp.eu/api/1/deliveries/user1/`);
const movie = res.json();
console.log(movie);
this.setState({
...this.state,
movie: movie.pk
});
} catch (e) {
console.log(e);
}
}
fetchMovie(); // Call async fundtion
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
}, () => {
console.log(this.state);
// Because setState is async function, so you have to console.log in callback of
// setState to see the new data of this.state
});
}
Input:
<div className="row">
<div className="col-sm-12">
<label id="p"> UZYTKOWNIK</label>
<input
type="text"
ref="user"
className="form-control"
name="user"
defaultValue={this.state.movie.id}
onChange={this.handleChange}
/>
<p>g {this.state.value}</p>
</div>

Categories