Yet another one of these questions. I know there are several here, but I can't see my mistake.
My code is based on "create-react-app" via Visual Studio 2019, so the boilerplate should look familiar.
I'm passing a function called "updateTodoCheckbox" from the parent FetchData component to my TodoItem component via a property called "onCheckboxUpdate.
When handleInputChange is called via the onChange for the checkbox (which has the name property isComplete), I get:
TypeError: this.props.onCheckboxUpdate is not a function
I have called bind on the function in the constructor. I've also tried passing it as an arrow function. Same result, so I don't think it's a binding issue.
Any help appreciated to see my error. I'm sure it's a simple one.
import React, { Component } from 'react';
export class TodoItem extends Component {
constructor(props) {
super(props);
this.state = {
isComplete: props.data.isComplete
}
this.handleInputChange = this.handleInputChange.bind(this); // otherwise this.setState will be undefined
}
handleInputChange(inputEvent) {
const target = inputEvent.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
if (name == "isComplete")
{
this.props.onCheckboxUpdate(); // This fails
}
this.setState({
[name]: value
});
}
render() {
return (
<div class="form-group" key={this.props.data.id}>
<div className="form-check">
<input class="form-check-input" type="checkbox" name="isComplete" checked={this.state.isComplete} id={this.props.data.id} onChange={this.handleInputChange}/>
<label class="form-check-label" for={this.props.data.id}>{this.props.data.name} - {this.props.data.categoryId}</label>
</div>
</div>
)
}
}
export class FetchData extends Component {
static displayName = FetchData.name;
constructor(props) {
super(props);
this.state = { todoData: [], loading: true };
this.updateTodoCheckbox = this.updateTodoCheckbox.bind(this);
}
componentDidMount() {
this.populateTodoData();
}
static renderTodoData(todoData) {
return (
<form>
{todoData.map(todo =>
<TodoItem data={todo}
onCheckboxUpdate={this.updateTodoCheckbox} />
)}
</form>
);
}
render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: FetchData.renderTodoData(this.state.todoData);
return (
<div>
<h1 id="tabelLabel" >TODO</h1>
<p>A list of things to do</p>
{contents}
</div>
);
}
async populateTodoData() {
const response = await fetch('api/TodoItems');
const data = await response.json();
this.setState({ todoData: data, loading: false });
}
async updateTodoCheckbox() {
console.log(test);
// some await call will go here
}
}
Problems looks to be the renderTodoData(todoData) render function as it is marked as static. Functions that have been marked as static do not have access to class scope variables/functions as they. Code placed in static scope can only acres other variables/functions that is also static (other static functions for example).
You should be ae to remove the static keyword from renderTodoData(todoData). You may need to bind it as you did with handleInputChange in your constructor.
Problem is static renderTodoData(todoData) function which is static and which, because of that, doesn't know about "this" (it can only access to the other static methods). Try not having that function as static.
Related
class Usurvey extends Component {
constructor(props) {
super(props);
this.nameRef = React.createRef();}
state={
uid:uuid.v1(),
studentName:'',
isSubmitted:false
}
nameSubmit=(e)=>{
let studName=this.nameRef.current.value;
console.log(studName);
this.setState({studentName:studName});
console.log(this.state);
}
render() {
let studentName;
if(this.state.studentName===''&&this.state.isSubmitted===false){
studentName=<div>
<h1>hey student pls let us know your name </h1>
<form onSubmit={this.nameSubmit}>
<input type='text' placeholder="name pls " ref={this.nameRef} className="namy" />
</form></div>}
return (
<div>
{studentName}
-------------------------------
{questions}
</div>
);
}
}
export default Usurvey;
see in this my question is that in namesubmit function when the form is submitted it should update the value of the state.studentName to the input given by the user and then log it on the console
and it is not working in above code but if i change the namsubmit function then it is working why ? pls explain
this is the new code
nameSubmit=(e)=>{
let studName=this.nameRef.current.value;
console.log(studName);
this.setState({studentName:studName},()=>{
console.log(this.state);
});
}
setState is async call. By doing like so-
this.setState({studentName:studName});
console.log(this.state)
State indeed gonna change in next render, but you are logging it before it happen.
At your new code
nameSubmit=(e)=>{
let studName=this.nameRef.current.value;
console.log(studName);
this.setState({studentName:studName},()=>{
console.log(this.state);
});
}
You set the log function as a callback, what gonna be executed only after setState finished it job
setState works asynchronously so the state of your component is not guaranteed to be updated right after the setState call. In your second snippet of code you provide the second argument as a callback function which is called after the state update so you can be sure the state is up-to-date when you log it into the console
setState is asynchronous, meaning the state might not be yet set by the time the next line is executed.
By using
this.setState({studentName:studName},()=>{
console.log(this.state);
});
You are defining a callback function and passing it to setState
()=> {
console.log(this.state);
});
setState will then, change the state, and only once it's done (which could be after nameSubmit is finished, it will execute your callback function, guarantying that the state is up to date on it.
I don't know what exactly is your coding doing but there is no questions variable defined in your code. So here is the full code:
import React from "react";
import { v1 } from "uuid";
export default class App extends React.Component {
constructor(props) {
super(props);
this.nameRef = React.createRef();
}
state = {
uid: v1(),
studentName: "",
isSubmitted: false
};
nameSubmit = e => {
let studName = this.nameRef.current.value;
this.setState({ studentName: studName }, () => {
console.log(this.state);
});
};
render() {
let studentName;
if (this.state.studentName === "" && this.state.isSubmitted === false) {
return (
<div>
<h1>hey student pls let us know your name </h1>
<form onSubmit={this.nameSubmit}>
<input type="text"
placeholder="name pls "
ref={this.nameRef}
className="namy"
/>
<button type="submit">Submit</button>
</form>
</div>
);
} else {
return <div>{this.state.studentName}</div>;
}
}
}
I am trying to upload some files via <input type="file"> to the state, in order to pass it back to the main component. With the code below I get the Uncaught TypeError: Cannot read property 'setState' of undefined when trying to update the state with the uploaded files.
import React, { Component } from 'react'
import * as RB from 'react-bootstrap'
import Button from 'components/Button/Button'
class uploadMob extends Component {
constructor(props) {
super(props)
this.state = {
files: [],
}
}
onFilesAdded(files) {
this.setState((prevState) => ({
files: prevState.files.concat(files),
}))
this.handleUpload()
}
handleUpload = (e) => {
const { pdfUploadToState } = this.props
debugger
pdfUploadToState(this.state.files)
console.log('PUSHED FILE', this.state.files)
}
render() {
const files = this.state.files
return (
<RB.Form.Group>
<div className="upload-btn-wrapper">
<div className="Files">
{files.map((file, key) => {
return (
<div key={key} className="Row">
<span className="Filename">
{file.value}
</span>
</div>
)
})}
</div>
<Button size="sm" variant="light">
Dateien hochladen
</Button>
<input
type="file"
name="file"
id="files"
onChange={this.onFilesAdded}
/>
</div>
</RB.Form.Group>
)
}
}
export default uploadMob
I would very much appreciate the help with this. It is driving me a bit crazy..
The problem is with this line:
onFilesAdded(files) {
You need to either bind() it to this like this:
constructor(props) {
super(props)
this.state = {
files: [],
};
this.onFilesAdded = this.onFilesAdded.bind(this);
}
or convert it to an arrow function:
onFilesAdded = files => {
The problem is this referred inside onFilesAdded does not point the component instance by default. By using the two methods above, we make sure that by calling this., the component is correctly referred.
You need to use an arrow function to have the correct value of this.
setState is asynchronous so you need to pass the this.handleUpload function as a callback in order to updload the files once setState is completed.
From the documentation :
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
onFilesAdded = files => {
this.setState(prevState => ({
files: [...prevState.files, ...files]
}), this.handleUpdload)
}
I am a novice in ReactJS. Was watching a tutorial that is recorded in ES6 JavaScript and simultaneously I am trying to recreate the same app in TypeScript (I am a novice in TypeScript too!!). In ES6 we need to use a class based approach if that particular component needs to maintain it's own state. I have installed "babel-plugin-transform-class-properties" so i do set state in ES6 as export default class SomeClass { state = {
someProperty : someValue }}. But in TypeScript I am using class based approach for every component. So here is a component that tries to maintain it's own state:
import React from 'react';
interface IAddOptionProps {
handleAddOption: (option: string) => string |null;
};
interface IAddOptionState {
error: any;
};
export default class AddOption extends React.Component<IAddOptionProps, IAddOptionState> {
handleAddOption = (e:any) => {
e.preventDefault();
const option = e.target.elements.option.value.trim();
const err : string = this.props.handleAddOption(option);
this.setState(() => {
return {
error: err
}
});
if (!err) {
e.target.elements.option.value = '';
}
}
render() {
console.log(this.state);
return (
<div>
{this.state.error != null ? <p>{this.state.error}</p> : ''}
<form onSubmit={this.handleAddOption}>
<div className="form-group">
<label>Enter an Option</label>
<input type="text" className="form-control" id="option" name="option"/>
</div>
<button className="btn btn-primary">Add option</button>
</form>
</div>
);
}
};
The statement console.log(this.state); and {this.state.error != null ? <p>{this.state.error}</p> : ''} inside render() is throwing error stating that Uncaught TypeError: Cannot read property 'error' of null. That means this.state is being set as null.
Why is state getting set to null and how do I resolve this ( In TypeScript )?
Thanks in Advance.
As already mentioned in the comments, you have to initialize the state either in the constructor or with a property initializer like this:
class AddOption extends React.Component<IAddOptionProps, IAddOptionState> {
this.state = {
error: ''
};
[...]
}
Otherwise state will be null and you will get an error like mentioned in your post.
You have to initialize your state. There are two ways to do that
The first is to use a property initializer
class AddOption extends React.Component<IAddOptionProps, IAddOptionState> {
this.state = {
error: ''
};
render() {
// Now you have your state! Do stuff here
}
}
The other way is to do it in the constructor
class AddOption extends React.Component<IAddOptionProps, IAddOptionState> {
constructor(props) {
super(props);
this.state = {
error: ''
};
}
render() {
// Now you have your state! Do stuff here
}
}
The solutions are equivalent, though the first is more elegant
Im novice to React js, i don't know whats wrong with below code, but i'm getting setState is not a function error.Please help me to fix this.
class AppBarLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
visibleSideBar:true,
slide:""
}
}
showProfile(){
this.setState({
slide:'slide'
});
console.log(this.state.slide);
}
render(){
return(
<div>
<header>
<NavBar show={this.showProfile}/>
<Profile slide={this.state.slide} />
</header>
</div>
);
}
}
export default AppBarLayout;
You need to bind this.showProfile in the component constructor
this.showProfile = this.showProfile.bind(this)
More detail about this on the Handling Events page of the React doc : https://facebook.github.io/react/docs/handling-events.html
Expanding on Delapouite's answer if you don't like to bind every function in the constructor you can use arrow functions to automatically bind to the correct context.
For example:
class AppBarLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
visibleSideBar:true,
slide:""
}
}
// Now showProfile is an arrow function
showProfile = () => {
this.setState({
slide:'slide'
});
console.log(this.state.slide);
}
render(){
return(
<div>
<header>
<NavBar show={this.showProfile}/>
<Profile slide={this.state.slide}/>
</header>
</div>
);
}
}
export default AppBarLayout;
In my case, I solved the problem without binding.
Declaring the method like this was generating the error:
async onSubmit(e) {
event.preventDefault();
this.setState({ shopEthereumAddress: e.target.id });
}
The CORRECT declaration which will not generate the error is this:
onSubmit = async event => {
event.preventDefault();
this.setState({ shopEthereumAddress: event.target.id });
}
This works.
toggleSwitch() {
this.setState({
name: 'Ram ji'
});
}
Using an arrow function keeps the context of this set to the parent scope. The main benifit of arrow functions apart from being more concise is
Main benefit: No binding of ‘this’
// use this arrow function instead of
toggleSwitch = () => {
this.setState({
name: 'Ram ji' //It's working
});
}
I cannot for the life of me figure out what is wrong with the following code, when a user adds a bug via the BugAdd form, the values are passed to the handleSubmit function which in turn should pass its props to addBug.
However, when I submit my form I see the 'console.log("Adding bug:", bug);'
But then after this I receive "react.min.js:14 Uncaught TypeError: Cannot read property 'bugs' of undefined", my initial thought was that perhaps I have missed a .bind somewhere.
Can anyone spot an issue with my code, it was working fine before refactoring to ES6
class BugAdd extends React.Component {
render() {
console.log("Rendering BugAdd");
return (
<div>
<form name="bugAdd">
<input type="text" name="owner" placeholder="Owner" />
<input type="text" name="title" placeholder="Title" />
<button onClick={this.handleSubmit.bind(this)}>Add Bug</button>
</form>
</div>
)
}
handleSubmit(e) {
e.preventDefault();
var form = document.forms.bugAdd;
this.props.addBug({owner: form.owner.value, title: form.title.value, status: 'New', priority: 'P1'});
// clear the form for the next input
form.owner.value = ""; form.title.value = "";
}
}
class BugList extends React.Component {
constructor() {
super();
this.state = {
bugs: bugData
}
}
render() {
console.log("Rendering bug list, num items:", this.state.bugs.length);
return (
<div>
<h1>Bug Tracker</h1>
<BugFilter />
<hr />
<BugTable bugs={this.state.bugs} />
<BugAdd addBug={this.addBug} />
</div>
)
}
addBug(bug) {
console.log("Adding bug:", bug);
// We're advised not to modify the state, it's immutable. So, make a copy.
var bugsModified = this.state.bugs.slice();
bug.id = this.state.bugs.length + 1;
bugsModified.push(bug);
this.setState({bugs: bugsModified});
}
}
When you extend React.Component with ES6 class, the component methods are not autobinded to this like when you use React.createClass. You can read more about this in the official documentation.
In your case, the cleanest solution is to bind the addBug method in the constructor to the component's this, like this:
class BugList extends React.Component {
constructor() {
super();
this.state = {
bugs: bugData
}
this.addBug = this.addBug.bind(this);
}
render() {
console.log("Rendering bug list, num items:", this.state.bugs.length);
return (
<div>
<h1>Bug Tracker</h1>
<BugFilter />
<hr />
<BugTable bugs={this.state.bugs} />
<BugAdd addBug={this.addBug} />
</div>
)
}
addBug(bug) {
console.log("Adding bug:", bug);
// We're advised not to modify the state, it's immutable. So, make a copy.
var bugsModified = this.state.bugs.slice();
bug.id = this.state.bugs.length + 1;
bugsModified.push(bug);
this.setState({bugs: bugsModified});
}
}
Now you will be able to access this.state.
try to define your addBug method like this with => which will auto bind to the class instance:
addBug = (bug) => {
console.log("Adding bug:", bug);
// We're advised not to modify the state, it's immutable. So, make a copy.
var bugsModified = this.state.bugs.slice();
bug.id = this.state.bugs.length + 1;
bugsModified.push(bug);
this.setState({bugs: bugsModified});
}
don't forget to add the Class properties transform to your babel
http://babeljs.io/docs/plugins/transform-class-properties/