I'm trying to run this code (picked up from some source) where inputs are created dynamically and values are recorded on submit.
I'm getting this error:
Cannot read property 'users' of undefined while calling createUI.
Can someone help me here, please?
import React, { useState } from "react";
import ReactDOM from "react-dom";
export default function App() {
const [state, setState] = useState({
users: [{ firstName: "", lastName: "" }]
});
const addClick = () => {
setState(prevState => {
users: [...prevState.users, { firstName: "", lastName: "" }];
});
};
const createUI = () => {
state.users.map((el, i) => {
return (
<div key={i}>
<input
placeholder="First Name"
name="firstName"
value={el.firstName || ""}
onChange={handleChange}
/>
<input
placeholder="Last Name"
name="lastName"
value={el.lastName || ""}
onChange={handleChange}
/>
<input type="button" value="remove" onClick={removeClick} />
</div>
);
});
};
const handleChange = (i, e) => {
const { name, value } = e.target;
let users = [...state.users];
users[i] = { ...users[i], [name]: value };
setState({ users });
};
const removeClick = i => {
let users = [...state.users];
users.splice(i, 1);
setState({ users });
};
const handleSubmit = event => {
alert("A name was submitted: " + JSON.stringify(this.state.users));
event.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
{createUI()}
<input type="button" value="add more" onClick={addClick()} />
<input type="submit" value="Submit" />
</form>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
// const rootElement = document.getElementById("root");
// ReactDOM.render(<App />, rootElement)
Sandbox here ->
https://codesandbox.io/s/dynamic-fields-23cw4?fontsize=14&hidenavigation=1&theme=dark
The reason for that error is the following line:
<input type="button" value="add more" onClick={addClick()} />
Technically you are calling the addClick function which changes the value of your state object. The solution which works fine is the following:
<input type="button" value="add more" onClick={() => addClick()} />
Or shorter way:
<input type="button" value="add more" onClick={addClick} />
I hope that helps!
There were 3 issues with your code,
you executed your onclick handler, addClick which triggered inifinite loop
createUI method did not return the array of created components
addClick function's setState syntax was not correct.
const {useState} = React;
function App() {
const [state, setState] = useState({
users: [{ firstName: "", lastName: "" }]
});
const addClick = () => {
setState(prevState => {
return { users: [...prevState.users, { firstName: "", lastName: "" }] };
});
};
const createUI = () => {
return state.users.map((el, i) => {
return (
<div key={i}>
<input
placeholder="First Name"
name="firstName"
value={el.firstName || ""}
onChange={(e)=>handleChange(e, i)}
/>
<input
placeholder="Last Name"
name="lastName"
value={el.lastName || ""}
onChange={(e)=>handleChange(e, i)}
/>
<input type="button" value="remove" onClick={removeClick} />
</div>
);
});
};
const handleChange = (e, i) => {
const { name, value } = e.target;
let users = [...state.users];
users[i] = { ...users[i], [name]: value };
setState({ users });
};
const removeClick = i => {
let users = [...state.users];
users.splice(i, 1);
setState({ users });
};
const handleSubmit = event => {
alert("A name was submitted: " + JSON.stringify(this.state.users));
event.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
{createUI()}
<input type="button" value="add more" onClick={addClick} />
<input type="submit" value="Submit" />
</form>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In addition to #norbiial's answer which should be the accepted answer: Your addClick function is incorrect. Your setState call is not returning anything which means as soon as you click the add button it will break again.
Change to this:
const addClick = () => {
setState(prevState => ({
users: [...prevState.users, { firstName: "", lastName: "" }]
}));
};
The same issue will be seen in createUI.
Error 1:
if you are returning an Object without return statement it should be wrapped in a bracket
const addClick = () => {
setState(prevState => ({
users: [...prevState.users, { firstName: "", lastName: "" }];
}));
};
Error 2:
Missing return statement,
const createUI = () => {
return state.users.map((el, i) => {
return (
<div key={i}>
<input
placeholder="First Name"
name="firstName"
value={el.firstName || ""}
onChange={handleChange}
/>
<input
placeholder="Last Name"
name="lastName"
value={el.lastName || ""}
onChange={handleChange}
/>
<input type="button" value="remove" onClick={removeClick} />
</div>
);
});
};
Error 3:
onClick should have a function, but you are setting a value returned from addClick which is undefined
...
<input type="button" value="add more" onClick={addClick} />
...
Related
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;
I am trying to check that the required fields are not empty and making sure that the input type is correct.
const CreateSensor = () => {
const [deveui, setDeveui] = useState('');
const [location, setLocation] = useState('');
const [levelid, setLevel] = useState('');
const submitValue = () => {
let data = {deveui,location,levelid};
//POST method
fetch("api")
ClearFields();
}
function ClearFields(){
document.getElementById("dev").value = "";
document.getElementById("location").value = "";
document.getElementById("level").value = "";
}
return(
<>
<hr/>
<input type="text" id="dev" placeholder="deveui" onChange={e => setDeveui(e.target.value)} />
<input type="text" id="location"placeholder="Location" onChange={e => setLocation(e.target.value)} />
<input type="text" id="level" placeholder="Levelid" onChange={e => setLevel(e.target.value)} />
<button onClick={submitValue}>Submit</button>
</>
)
}
the submit button will check whether deveui is not empty and the levelid is set to an integer.
I have tried changing the input type for levelid to numbers but there is arrows on it which I feel is unnecessary.
I strongly recommend using a React form library. Here's an example with react-hook-form
import { useForm } from "react-hook-form";
const CreateSensor = () => {
const {
register,
handleSubmit,
watch,
reset,
formState: { errors },
} = useForm({ defaultValues: { deveui: "", location: "", levelid: "" } });
const submitValue = ({deveui, location, levelid}) => {
// exclude 'deveui' from fetch payload
const payload = { location, levelid }
// POST data to api, for example
fetch("https://myapi.com", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
// reset form state
}).then((response) => reset());
};
return (
<>
<hr />
<form onSubmit={handleSubmit(submitValue)}>
<input
{...register("deveui", { required: true })}
type="text"
id="dev"
placeholder="deveui"
/>
<input
{...register("location", { required: true })}
type="text"
id="location"
placeholder="Location"
/>
<input
{...register("levelid", { required: true })}
type="text"
id="level"
placeholder="Levelid"
/>
<button type="submit">Submit</button>
</form>
</>
);
}
class SingleProduct extends React.Component {
constructor(props) {
super(props);
this.state = {
edit: false,
name: "",
imageUrl: null,
price: 0,
description: "",
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.onImageChange = this.onImageChange.bind(this);
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value,
});
}
onImageChange = (event) => {
if (event.target.files && event.target.files[0]) {
let image = event.target.files[0];
console.log(image.src);
this.setState({
imageUrl: URL.createObjectURL(image),
});
}
};
handleSubmit(event) {
event.preventDefault();
console.log(this.props.cocktail);
this.props.updateCocktail({ ...this.props.cocktail, ...this.state });
this.setState({
edit: false,
});
}
render() {
const { cocktail, isAdmin } = this.props;
const { edit, name, price, description, imageUrl } = this.state;
const { handleChange, handleSubmit, onImageChange } = this;
return (
<div>
{isAdmin && (
<button
onClick={() =>
this.setState((prevState) => ({ edit: !prevState.edit }))
}
>
Edit Cocktail
</button>
)}
{edit ? (
<form>
<label>
Name:
<input
type="text"
name="name"
onChange={handleChange}
value={name}
/>
</label>
<label>
Upload Image:
<input type="file" name="imageUrl" onChange={onImageChange} />
<img src={imageUrl} />
</label>
<label>
Description:
<input
type="text"
name="description"
onChange={handleChange}
value={description}
/>
</label>
<label>
Price:
<input
type="text"
name="price"
onChange={handleChange}
value={price}
/>
</label>
<button type="submit" onClick={(event) => handleSubmit(event)}>
Submit
</button>
</form>
) : (.....stuff)}
</div>
);
}
}
I am attempting to a "file" button in my form and it does allow me to successfully add image and it displays that change in the backend. However, once I reload the page, I am thrown this error "GET blob:http://localhost:3000/3f9fwf19-c526-4404-bb-20a433542ed net::ERR_FILE_NOT_FOUND" and the image doesn't render.
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}
/>
There's a plus button to increase the number of textfields in the page. After increasing the textfields, I want to show all the textfield input value at once under, after clicking on the submit button.
But here it's giving a problem on the onChange setState issue. How to handle it ? Any help ?
Here's the => DEMO
class App extends Component {
constructor(props) {
super(props);
this.state = {
arr: [],
firstname: '',
lastname: '',
setarr: [],
temp: []
};
this.addTextfields = this.addTextfields.bind(this);
this.changeFirstname = this.changeFirstname.bind(this);
this.changeLastname = this.changeLastname.bind(this);
}
addTextfields(e) {
let htmlContent = [];
htmlContent.push(
<div>
<input type="text" placeholder="first name" onChange={this.changeFirstname} value={this.state.firstname} /> <br/> <br/>
<input type="text" placeholder="last name" onChange={this.changeLastname} value={this.state.lastname} /> <br/> <br/>
</div>
);
this.setState({ arr: this.state.arr.concat(htmlContent) });
}
changeFirstname(e) {
this.setState({ firstname: e.target.value });
}
changeLastname(e) {
this.setState({ lastname: e.target.value });
}
showTexts() {
console.log(this.state.firstname, this.state.lastname);
var add = [this.state.firstname, this.state.lastname];
this.state.temp = this.state.temp.concat(add);
this.setState({ setarr: this.state.temp, firstname: '', lastname: '' });
// localStorage.setItem(this.state.arr, 'names');
}
render() {
return (
<div>
<div className="App">
<input type="text" onChange={this.changeFirstname} placeholder="first name" value={this.state.firstname} /> <br/> <br/>
<input type="text" onChange={this.changeLastname} placeholder="last name" value={this.state.lastname} /> <br/> <br/>
<button type="submit" onClick={this.addTextfields}>Plus</button>
{this.state.arr}
</div>
<div className="submit-button">
<button type="submit" onClick={this.showTexts.bind(this)}>Submit</button>
</div>
<div>
{this.state.setarr}
</div>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Do it this way.
import React, { Component } from "react";
import { render } from "react-dom";
class App extends Component {
constructor(props) {
super(props);
this.state = {
arr: [
{
firstname: "",
lastname: ""
}
],
displayValues: []
};
this.addTextfields = this.addTextfields.bind(this);
this.changeFirstname = this.changeFirstname.bind(this);
this.changeLastname = this.changeLastname.bind(this);
}
addTextfields(e) {
let arr = this.state.arr;
arr.push({
firstname: "",
lastname: ""
});
this.setState({ arr });
}
changeFirstname(e, index) {
let arr = this.state.arr;
arr[index].firstname = e.target.value;
this.setState({ arr });
}
changeLastname(e, index) {
let arr = this.state.arr;
arr[index].lastname = e.target.value;
this.setState({ arr });
}
showTexts() {
let displayValues = [];
this.state.arr.map(element => {
console.log(element.firstname, element.lastname);
var add = element.firstname + " " + element.lastname;
displayValues.push(add);
});
this.setState({ displayValues });
}
render() {
return (
<div>
<div className="App">
{this.state.arr.map((element, index) => {
return (
<div key={index}>
<input
type="text"
onChange={e => this.changeFirstname(e, index)}
placeholder="first name"
value={this.state.firstname}
/>{" "}
<br /> <br />
<input
type="text"
onChange={e => this.changeLastname(e, index)}
placeholder="last name"
value={this.state.lastname}
/>{" "}
<br /> <br />
</div>
);
})}
<button type="submit" onClick={this.addTextfields}>
Plus
</button>
</div>
<div className="submit-button">
<button type="submit" onClick={this.showTexts.bind(this)}>
Submit
</button>
</div>
<div>
{this.state.displayValues.map(element => {
return <p>{element}</p>;
})}
</div>
</div>
);
}
}
render(<App />, document.getElementById("root"));
I think, you are doing it the wrong way. I have created the below sample for reference. Please have a look.
const Form = (props) => {
return(
<div>
<input type="text" placeholder="first name" onChange={props.changeFirstname} value={props.firstname} /> <br/> <br/>
<input type="text" placeholder="last name" onChange={props.changeLastname} value={props.lastname} /> <br/> <br/>
</div>
)
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
arr: [
{
firstname: '',
lastname: '',
id:1
}
],
showOutput:false
};
this.addTextfields = this.addTextfields.bind(this);
this.changeFirstName = this.changeFirstName.bind(this);
this.changeLastName = this.changeLastName.bind(this);
}
addTextfields(e) {
debugger
let oldArray = this.state.arr;
this.setState((prevState) => {
return {
arr: oldArray.concat({
firstname: '',
lastname: '',
id: prevState.arr.length + 2
})
}
});
}
changeFirstName(e, selectedIndex) {
let updatedArray = this.state.arr.map((data) => {
if(data.id === selectedIndex) {
return Object.assign({}, data, {
firstname: e.target.value
})
} else {
return data
}
})
this.setState({ arr: updatedArray });
}
changeLastName(e, selectedIndex) {
let updatedArray = this.state.arr.map((data) => {
if(data.id === selectedIndex) {
return Object.assign({}, data, {
lastname: e.target.value
})
} else {
return data
}
})
this.setState({ arr: updatedArray });
}
showTexts() {
this.setState({
showOutput: true
})
}
render() {
return (
<div>
<div className="App">
{
this.state.arr.map((data, index) => {
return <Form key={index} data={data}
changeFirstname={(e) => this.changeFirstName(e, data.id)}
changeLastname={(e) => this.changeLastName(e, data.id)}
/>
})
}
<button type="submit" onClick={this.addTextfields}>Plus</button>
</div>
<div className="submit-button">
<button type="submit" onClick={this.showTexts.bind(this)}>Submit</button>
</div>
<div>
{
this.state.showOutput &&
this.state.arr.map((data, index) => {
return <div>
{data.firstname} {data.lastname}
</div>
})
}
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Hope this helps.