Value of this is undefined - javascript

I have a this value inside an if statement, nested inside a my handleFormChange function. I've tried to use arrow functions with this function to bind the value of this but im getting the following error message:
TypeError: Cannot set property 'author' of undefined
From my understanding usually you find the this value by looking at where the function containing this is called. However, in my case im struggling to work this out. Can anyone explain to me why it is undefined and how to solve this issue? Here is the code:
class CommentForm extends React.Component{
constructor(props){
super(props)
var comment={author:'', message:''}
}
handleSubmit= (e)=>{
e.preventDefault()
var authorVal = this.comment.author;
var textVal = this.comment.message;
//this stops any comment submittal if anything missing
if (!textVal || !authorVal) {
return;
}
this.props.onCommentSubmit(this.comment);
//reset form values
e.target[0].value = '';
e.target[1].value = '';
return;
}
handleFormChange= (e)=>{
e.preventDefault()
if(e.target.name==='author'){
var author = e.target.value.trim();
this.comment.author = author
}else if(e.target.name==='message'){
var message = e.target.value.trim();
this.comment.message = message
}
}
render() {
return (
<form className = "ui form" method="post" onChange={(e)=>{this.handleFormChange(e)}} onSubmit={(e)=>{this.handleSubmit(e)}}>
<div className="form-group">
<input
className="form-control"
placeholder="user..."
name="author"
type="text"
/>
</div>
<div className="form-group">
<textarea
className="form-control"
placeholder="comment..."
name="message"
/>
</div>
<div className="form-group">
<button disabled={null} className="btn btn-primary">
Comment ➤
</button>
</div>
</form>
);
}
}
export default CommentForm

The first step into learning how to do what you want is to study how React's State works (official docs are great at explaning it).
This example is not complete, but should guide you through the proccess.
class CommentForm extends Component {
constructor(props) {
super(props);
this.state = {
author : '',
message : '',
}
this.onChangeAuthorName = this.onChangeAuthorName.bind(this);
this.onBlurAuthorName = this.onBlurAuthorName.bind(this);
}
onChangeAuthorName(e) {
this.setState({ author: e.target.value });
}
onBlurAuthorName() {
// trim on blur (or when you send to the network, to avoid
// having the user not being able to add empty whitespaces
// while typing
this.setState({ author: this.state.author.trim() })
}
render() {
return (
...
<input value={this.state.author} onChange={this.onChangeAuthorName} onBlur={this.onBlurAuthorName} />
...
);
}
}
Usually, when you want to "set" variables in React, you don't add them as you do to in Javascript classes (this.comment = e.target.value), but instead, use the function setState(). From the docs:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
(NOTE: Alternatively, this could be done using React Hooks, but I recommend you learn the lifecycle methods firsthand. Good luck!)

I decided to write even if you proposed the correct answer for the simple reason that I think my code is closer to what it published.
import React, { Component } from "react";
import ReactDOM from "react-dom";
class App extends Component {
constructor(props) {
super(props);
this.state = {
comment: {},
some: 1
};
}
handleFormChange = e => {
e.preventDefault();
let { comment } = this.state;
const newCommentState = function() {
let returnObj = { ...comment };
returnObj[this.target.name] = this.target.value.trim();
return returnObj;
}.bind(e)();
this.setState({ comment: newCommentState });
};
handleSubmit = e => {
e.preventDefault();
let { comment } = this.state;
if (!comment.author || !comment.message) return;
this.props.onCommentSubmit(comment);
this.setState({ comment: {} });
e.target[0].value = "";
e.target[1].value = "";
};
render() {
return (
<div>
<form
className="ui form"
method="post"
onChange={e => {
this.handleFormChange(e);
}}
onSubmit={e => {
this.handleSubmit(e);
}}
>
<div className="form-group">
<input
className="form-control"
placeholder="user..."
name="author"
type="text"
/>
</div>
<div className="form-group">
<textarea
className="form-control"
placeholder="comment..."
name="message"
/>
</div>
<div className="form-group">
<button disabled={null} className="btn btn-primary">
Comment ➤
</button>
</div>
</form>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Live example:

Related

Conditional rendering on select

I am pretty new to the wonderful world of React.
I have two inputs passing data through from an API that renders a list of options. And I want to send the selected inputs from those options back to the parent in the input fields to display for another search.
I have tried passing state down to them and render them them optionally with both a ternary and an if else statement in the "SearchCityList" component in several ways but I either get both lists rendered and they would have to choose between one list that is doubled to put in each input field or it only puts the selected value in one input. Would appreciate any & all suggestions Thanks!
class Form extends Component {
state = {
showComponent: false,
showComponent2: false,
};
// open/close control over SearchCity component box
openSearch = () => {
this.setState({ showComponent: true });
};
openSearch2 = () => {
this.setState({ showComponent2: true });
};
closeSearch = () => {
this.setState({
showComponent: false,
showComponent2: false
});
};
// Passed down cb function to get selected city search in selectCity component
GoingTo = (flights) => {
this.setState({ GoingTo: [flights] });
};
LeavingFrom = (flights) => {
this.setState({ LeavingFrom: [flights] });
};
render() {
return (
<div>
<form className="form-fields container">
<div className="inputs">
<h1>Search for a flight!</h1>
<div className="depart">
<input
onClick={this.openSearch}
className="flight-search"
placeholder="Leaving From"
value={this.state.LeavingFrom}
></input>
<input type="date"></input>
</div>
<div className="Returning">
<input
onClick={this.openSearch2}
className="flight-search"
placeholder="Going To "
value={this.state.GoingTo}
></input>
<input type="date" placeholder="Returning"></input>
</div>
</div>
<button>Check Flights!</button>
</form>
{this.state.showComponent || this.state.showComponent2 ? (
<SearchCity
openSearch={this.openSearch}
openSearch2={this.openSearch2}
flightSearch={this.state.flightSearch}
closeSearch={this.closeSearch}
GoingTo={this.GoingTo}
LeavingFrom={this.LeavingFrom}
onSearchSubmission={this.onSearchSubmission}
closeSearch={this.closeSearch}
/>
) : null}
</div>
);
}
}
export default Form;
class SearchCity extends Component {
state = {
LeavingFrom: "",
GoingTo: "",
search: "",
flightSearch: [],
};
// Search submission / api call
onSearchSubmission = async (search) => {
const response = await Axios.get(
{
headers: {
"
useQueryString: true,
},
}
);
// set New state with array of searched flight data sent to searchCity component
const flightSearch = this.setState({ flightSearch: response.data.Places });
};
// Callback function to send search/input to parent "Form" component
submitSearch = (e) => {
e.preventDefault();
this.onSearchSubmission(this.state.search);
};
// closeSearch callback function sent from Form component to close pop up search box when X is pressed
closeSearch = () => {
this.props.closeSearch();
};
render() {
return (
<div className="container search-list">
<form onChange={this.submitSearch}>
<i className="fas fa-times close-btn" onClick={this.closeSearch}></i>
<input
onChange={(e) => this.setState({ search: e.target.value })} //query-search api
value={this.state.search}
className="search-input"
type="text"
placeholder="Search Locations"
></input>
<div className="search-scroll">
<SearchCityList
openSearch={this.props.openSearch}
openSearch2={this.props.openSearch2}
LeavingFrom={this.props.LeavingFrom}
GoingTo={this.props.GoingTo}
flightSearch={this.state.flightSearch}
/>
</div>
</form>
</div>
);
}
}
export default SearchCity;
function SearchCityList({ flightSearch, LeavingFrom, GoingTo }) {
const renderList = flightSearch.map((flights) => {
return (
<div>
<SelectCityLeaving LeavingFrom={LeavingFrom} flights={flights} />
<SelectCityGoing GoingTo={GoingTo} flights={flights} />
</div>
);
});
return <div>{renderList}</div>;
}
export default SearchCityList;
First of all, when dealing with state, make sure you initialize in the constructor and also ensure you bind your handlers to this component instance as this will refer to something else in the handlers if you don't and you won't be able to call this.setState().
constructor(props) {
super(props); // important
state = {
// your state
};
// make sure to bind the handlers so `this` refers to the
// component like so
this.openSearch = this.openSearch.bind(this);
}

Why does my React form auto-refresh the page even if I put "event.preventDefault()" on handleSubmit?

I have two files which work together to render things. The first is App.js, which first renders Form.js. The form will then collect information, which on submission, changes the Form state and calls a function from App.js. This function is called "createProject." Calling "createProject" in Form.js "handleSubmit" makes the page auto-refresh. However, if I remove "createProject" from handleSubmit, the page does not auto-refresh. Here are the two files.
import React, { Component } from "react";
import Project from "./components/Project.js"
import Form from "./Form.js";
class App extends Component {
constructor(props) {
super(props);
this.state = {
projectList: [],
myProjects: [],
userList: [],
submitted: false
};
this.createProject = this.createProject.bind(this);
}
createProject(title, desc, langs, len, exp) {
this.setState({
projectList: this.state.projectList.push([
{
title : title,
description : desc,
language : langs,
length : len,
experience : exp
}
]),
submitted : true
});
}
deleteProject(title) {
const projects = this.state.projectList.filter(
p => p.title !== title
);
this.setState({projects});
}
render() {
let info;
if (this.state.submitted) {
info = (
<div>
<p>cccc</p>
</div>
);
} else {
info = (
<br/>
);
}
return(
<div>
<Form/>
{info}
{this.state.projectList.map((params) =>
<Project {...params}/>)}
</div>
);
}
}
export default App;
import React from "react";
import createProject from "./App.js"
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
description: "",
language: "",
length: 0,
experience: "",
submitted: false
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleSubmit(event) {
this.setState({
submitted: true
})
createProject(
this.state.title,
this.state.description,
this.state.language,
this.state.length,
this.state.experience
)
event.preventDefault();
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
let info;
if (this.state.submitted) {
info = (
<div>
<h1>{this.state.title}</h1>
<p>{this.state.description}</p>
<p>{this.state.language}</p>
<p>{this.state.length}</p>
<p>{this.state.experience}</p>
</div>
);
} else {
info = <br/>;
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Title:
<input
name="title"
type="textbox"
checked={this.state.title}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Description:
<input
name="description"
type="textbox"
checked={this.state.description}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Language:
<input
name="language"
type="textbox"
checked={this.state.language}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Length:
<input
name="length"
type="number"
checked={this.state.length}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Experience:
<input
name="experience"
type="textbox"
checked={this.state.experience}
onChange={this.handleInputChange} />
</label>
<br />
<input type="submit" value="Submit" />
</form>
{info}
</div>
);
}
}
export default Form;
I've also tried adding "new" to the "createProject" in handleSubmit, and while that does stop the auto-refresh, it will not call the createProject function. (Or maybe it does, but none of the code in the createProject function seems to be run.) Can anyone help with preventing this auto refresh while also allowing App's createProject function to run properly?
The page auto refreshes because execution never gets to your event.PreventDefault() line. This is due to an error encountered when react tries to evaluate createProject. To fix this, correct handleSubmit like so.
handleSubmit(event) {
event.preventDefault(); // moved up in execution.
this.setState({
submitted: true
})
createProject(
this.state.title,
this.state.description,
this.state.language,
this.state.length,
this.state.experience
)
}
Notice that moving event.PreventDefault() to the top of your handleSubmit(event) function just before this.setState line prevents default form behaviour on submit.
You however get an error because App.js doesn't export a function called createProject. To maintain the createProject within App instance, you need to pass it as a prop which you can then reference as this.props.createProject.
See this answer on how to do call a Parent method in ReactJS.

How to pass default value loaded into form input to the payload

I'm making an edit form which inside Modal. Form inputs has defaultValue that comes from props with an name, age and strength as default value. If I change all inputs value, everything works fine, but if I change only one input value when I console.log payload I got and empty strings, but only value that I changed goes to the payload. How can I make that even I change only one input payload gets inputs default value from props?
My code is here:
import React, { Component } from 'react';
import './Form.scss';
import axios from 'axios';
class Form extends Component {
constructor(props){
super(props);
this.state = {
id: this.props.gnomeId,
submitedName: '',
submitedAge: '',
submitedStrength: ''
}
this.onSubmit = this.onSubmit.bind(this);
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
});
}
onSubmit(event) {
event.preventDefault();
const gnome = {
name: this.state.submitedName,
age: this.state.submitedAge,
strenght: this.state.submitedStrength,
}
console.log(gnome);
axios.post(`${API}/${this.state.id}`, {gnome})
.then( res => {
console.log(res);
console.log(res.data);
});
}
render() {
return(
<form onSubmit={this.onSubmit}>
<div>
<label htmlFor="name">Name</label>
<input type="text" name="submitedName" id="name" defaultValue={this.props.gnomeName} onChange={this.handleChange}/>
</div>
<div>
<label htmlFor="age">Age</label>
<input type="text" name="submitedAge" id="age" defaultValue={this.props.gnomeAge} onChange={this.handleChange}/>
</div>
<div>
<label htmlFor="strength">Strength</label>
<input type="text" name="submitedStrength" id="strength" defaultValue={this.props.gnomeStrength} onChange={this.handleChange}/>
</div>
<button type="submit" className="submit-btn">Submit</button>
</form>
)
}
}
export default Form;
Just set the initial state to the default values:
super(props);
this.state = {
id: this.props.gnomeId,
submitedName: props.gnomeName,
submitedAge: props.gnomeAge,
submitedStrength: props.gnomeStrength
}
You may use Nullish_Coalescing_Operator to give different value from init as null/undefined
this.state = {
submitedName: null,
...
}
const gnome = {
name: this.state.submitedName ?? this.props.submitedName,
...
}
First remove the defaultProps.
add componentDidUpdate in class based component and useEffect in function based component
componentDidUpdate(props) {
if(props){
this.state = {
id: this.props.gnomeId,
submitedName: props.gnomeName,
submitedAge: props.gnomeAge,
submitedStrength: props.gnomeStrength
}
}
}
it is necessary to put props in if because it has undefined initial so not to get error

Getting value from react component

I have a component InputArea with state = {input: ''}
Then I map several of these components in a container and write them in state = {inputAreas: []}
Now, how can I get inputs in the container? Logging this.state.inputAreas[0] returns object like this:
{$$typeof: Symbol(react.element), type: ƒ, key: "1", ref: null, props:
{…}, …}
In elements it shows like this:
<input type="text" class="form-control" name="input" value="abc">
Using this.state.prefooterArea[0].value gives undefined.
I also tried passing input from component to container as props, but it says getInput is not a function. From what I understood it has something to do with the fact I used map in the container. I can't use redux in this project.
Code of component
class PrefooterAreaInput extends Component {
state = {
input: ''
}
textChangedHandler = (event) => {
let newState = {};
newState[event.target.name] = event.target.value;
this.setState(newState);
}
render() {
return (
<div>
<input
className="form-control"
type="text"
name="input"
value = {this.state.input}
onChange={this.textChangedHandler}
/>
</div>
)
}
}
Code of container
class DescriptionFrame extends Component {
state = {,
prefooterArea: [<PrefooterAreaInput key={1}/>]
};
addFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length < prefooterInputFieldsMax) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.push(
<PrefooterAreaInput key={this.state.prefooterArea.length + 1} />
);
this.setState({ prefooterArea: newPrefooterArea });
}
};
removeFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length > 1) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.splice(newPrefooterArea.length - 1);
this.setState({ prefooterArea: newPrefooterArea });
}
render() {
// want to get this.state.prefooterArea[0]'s value
return (
<div>
{this.state.prefooterArea}
<a
className="nav-link"
href=""
onClick={this.addFooterInputHandler}
>
Add More
</a>
<a
className="nav-link"
href=""
onClick={this.removeFooterInputHandler}
>
Remove Last
</a>
</div>
);
}
}
Figured it out. This caused problem.
prefooterArea: [<PrefooterAreaInput key={1}/>]
I should have added that initial PrefooterAreaInput with lifecycle method instead. With that I was able to pass state just fine.
Are you trying to achieve something like this ?
child component :
export default class InputBox extends React.Component {
render() {
return (
<input onChange={event => this.props.onChange(event.target.value)} />
);
}}
parent component :
import InputBox from './InputBox';
class FilterBar extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: "" //get input value from state this input
};
this.updateFilters = this.updateFilters.bind(this);
}
updateFilters(i) {
this.setState({ inputs: i }); // this will print whatever input you type
}
render() {
return (
<div>
<InputBox onChange={(i) => this.updateFilters(i)} />
</div>
);
}
}

ReactJS: Getting inputs from form

I am currently trying to get the complete input from a user in a form using React. I need to get these inputs and then store them so that I can pass these values to another function. Currently, I have been trying to use uncontrolled inputs without success, but have also tried controlled inputs without any success either. Any ideas? I have to pass these values to the function peopleContract.addPerson(this._firstName, this._lastName, this._email, {from: accounts[1], gas: 3000000})
Here is the code (commented is the controlled input approach):
import React from 'react';
import Web3 from 'web3';
//Declaring the ethereum client (initializing) with the url in which the testrpc is running
var ETHEREUM_CLIENT = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
//These could be dynamically added through input fields, but hard coding for now
var peopleContractABI = [{"constant":true,"inputs":[],"name":"getPeople","outputs":[{"name":"","type":"bytes32[]"},{"name":"","type":"bytes32[]"},{"name":"","type":"bytes32[]"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"people","outputs":[{"name":"firstName","type":"bytes32"},{"name":"lastName","type":"bytes32"},{"name":"email","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_firstName","type":"bytes32"},{"name":"_lastName","type":"bytes32"},{"name":"_email","type":"bytes32"}],"name":"addPerson","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]
var peopleContractAddress = '0xb1a711f4e1250761b85be7bb4478c07d256b8225'
var peopleContract = ETHEREUM_CLIENT.eth.contract(peopleContractABI).at(peopleContractAddress)
//Need to create a variable named accounts in order to know which account
//to make the transactions from
var accounts = ETHEREUM_CLIENT.eth.accounts
//Creating the dynamic input fields for the user to input his/her data
export class Form extends React.Component{
handleSubmitClick = () => {
const firstName = this._firstName.value;
const lastName = this._lastName.value;
const email = this._email.value;
//do something with these variables
}
/*
handleChange(event) {
this.setState({[key]: event.target.value});
}
*/
/*
handleChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handleSubmit(event) {
alert('A user was submitted: ' + this.state.firstName + this.state.lastName + this.state.email);
event.preventdefault();
*/
/*
if((this.state.firstName==!"") && (this.state.lastName==!"")&& (this.state.email==!"")){
peopleContract.addPerson(this.state.firstName, this.state.lastName, this.state.email, {from: accounts[1], gas: 3000000})
// after you subimt values clear state
this.setState({
firstName: this.state.firstName,
lastName: this.state.lastName,
email: this.state.email
})
}else{
// render error
alert('Some fields are mandatory');
}
}
*/
/*
componentWillMount(){
peopleContract.addPerson(this._firstName, this._lastName, this._email, {from: accounts[1], gas: 3000000})
}
*/
render() {
peopleContract.addPerson(this._firstName, this._lastName, this._email, {from: accounts[1], gas: 3000000})
return(
<form>
<div>
<h4>Name</h4>
<input
type="text"
ref={input => this._firstName = input} />
</div>
<div>
<h4>Last Name</h4>
<input
type="text"
ref = {input2 => this._lastName = input2} />
</div>
<div>
<h4>Email</h4>
<input
type="text"
ref = {input3 => this._email = input3} />
</div>
<button onClick={this.handleSubmitClick}>Submit</button>
</form>
);
}
}
You are trying to use the refs before they are assigned in the render function.
It seems like you want to call peopleContract.addPerson() on submit so it should look like this
export class Form extends React.Component{
handleSubmitClick = () => {
const firstName = this._firstName.value;
const lastName = this._lastName.value;
const email = this._email.value;
peopleContract.addPerson(firstName, lastName, email, {from: accounts[1], gas: 3000000})
}
render() {
return(
<form>
<div>
<h4>Name</h4>
<input
type="text"
ref={input => this._firstName = input} />
</div>
<div>
<h4>Last Name</h4>
<input
type="text"
ref = {input2 => this._lastName = input2} />
</div>
<div>
<h4>Email</h4>
<input
type="text"
ref = {input3 => this._email = input3} />
</div>
<button onClick={this.handleSubmitClick}>Submit</button>
</form>
);
}
}
By using ref callback we store the reference of dom element, As per DOC:
When the ref attribute is used on an HTML element, the ref callback
receives the underlying DOM element as its argument. For example, this
code uses the ref callback to store a reference to a DOM node:
ref = { (input) => { this.textInput = input; }} />
To get the values of uncontrolled component using ref you need to write:
this._firstName.value, //value
this._lastName.value, //value
this._email.value //value
Another change is remove this line from render method:
peopleContract.addPerson(this._firstName, this._lastName, this._email, {from: accounts[1], gas: 3000000})
Because during initial rendering ref will not be available, so it you try to access the value before rendering it will throw error.
The ref attribute takes a callback function, and the callback will be
executed immediately after the component is mounted or unmounted.
Check the working solution:
class Form extends React.Component{
handleSubmitClick() {
const firstName = this._firstName.value;
const lastName = this._lastName.value;
const email = this._email.value;
console.log(firstName, lastName,email);
peopleContract.addPerson(firstName, lastName, email, {from: accounts[1], gas: 3000000})
}
render() {
return(
<form>
<div>
<h4>Name</h4>
<input
type="text"
ref={input => this._firstName = input} />
</div>
<div>
<h4>Last Name</h4>
<input
type="text"
ref = {input2 => this._lastName = input2} />
</div>
<div>
<h4>Email</h4>
<input
type="text"
ref = {input3 => this._email = input3} />
</div>
<button onClick={this.handleSubmitClick.bind(this)}>Submit</button>
</form>
);
}
}
ReactDOM.render(<Form/>, document.getElementById('app'))
<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='app'/>

Categories

Resources