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/
Related
I'm planning to add a prefilled form with React. I have the actual data on props. This is what I came up with.
#connect(...)
class Some extends React.Component {
state = {
...this.props.auth.user
}
render() {
// Create a form using the data on state
}
}
It looks not correct since I'm not using a react lifecycle hook here. I would like to ask if there is a better practice to achieve what I'm trying to do.
I am not sure about your architecture,since you are using uncontrolled component here, it is recommended to keep the source of truth at one place.
you can do something like this:
#connect(...)
class Some extends React.Component {
constructor(props) {
super(props);
this.state = {
userName:this.props.auth.user
}
}
handleChange = (event) => {
this.setState({userName: event.target.value});
}
render() {
return(
<div>
<input onChange={this.handleChange} id="some" type="text" value= {this.state.userName}/>
</div>
)
}
}
If you want to use controlled component that is controlled through parent/container. you can manage the values through props and set the props onChange.
So to elaborate on my previous responses you would do something like this to achieve what you want:
#connect(...)
class Some extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
}
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
render() {
return(
<div>
<input onChange={this.handleChange} id="some" type="text" value= {this.state.value|| this.props.value}/>
</div>
)
}
}
While your value is an empty string (in the state), the fields will be populated from your props and as soon as you start typing it will overwrite the prepopulated values with the ones in your state.
Best practices would be to actually have a Component that handles this logic and then passes the props to the form that should be just a dumb presentational component:
class SomeController extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
}
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
return (<Form handleChange={this.handleChange} value={this.state.value} />)
}
And then your form component:
const Form = (props) => (
<form>
<input onChange={props.handleChange} value={props.value} />
</form>
);
Hope this explanation helps.
I want to add the user to the options array which I have defined inside the state. Problem is whenever I use prevstate.options.concat([username]). it shows this error. While when I just push username using this.state.push(username) to the array no error occurs. But for good practice in React we never alter directly the state variable, that's why I was using prevstate so I want to know why I am getting this error.
As I am getting value of user and this.state.options also showing in the console when I declare inside adduserso I want to know why my setState is not working using prevstate variable?
class Login extends React.Component{
constructor(props){
super(props);
this.addUser = this.addUser.bind(this)
this.State = {
options : ['hello']
}
}
addUser(username){
console.log(username)
console.log(this.State.options)
this.State.options.push(username)
this.setState((prevState)=>{
// console.log(prevState.options)
return {
options : prevState.options.concat([username])
}
})
}
render(){
return(
<div>
<Form addUser = {this.addUser}/>
</div>
);
}
}
class Form extends React.Component{
constructor(props){
super(props);
this.formData = this.formData.bind(this);
}
formData(e){
e.preventDefault();
const username = e.target.elements.user.value.trim();
console.log(username);
this.props.addUser(username);
}
render(){
return(
<div>
<form onSubmit={this.formData}>
Name: <input type="text" name="user"></input>
<input type ="submit"></input>
</form>
</div>
)
}
}
ReactDOM.render(<Login /> , document.getElementById('app'));
actual result would be to concat a user in the options array using setState
Okay I have mocked your component in a sandBox and the following seems to be working. Here is the sandBox: https://codesandbox.io/s/mq3qyvvjkx
You're issue seems to have been using this.State instead of this.state (lower case s) multiple times in your component. And this looks why you were receiving your error.
class Login extends React.Component {
constructor(props) {
super(props);
this.addUser = this.addUser.bind(this);
this.state = {
options: ["hello"]
};
}
addUser(username) {
console.log(username);
console.log(this.state.options);
this.state.options.push(username);
this.setState(prevState => {
// console.log(prevState.options)
return {
options: prevState.options.concat([username])
};
});
}
render() {
return (
<div>
<Form addUser={this.addUser} />
</div>
);
}
}
class Form extends React.Component {
constructor(props) {
super(props);
this.formData = this.formData.bind(this);
}
formData(e) {
e.preventDefault();
const username = e.target.elements.user.value.trim();
console.log(username);
this.props.addUser(username);
}
render() {
return (
<div>
<form onSubmit={this.formData}>
Name: <input type="text" name="user" />
<input type="submit" />
</form>
</div>
);
}
}
The following produces the following console outputs, with no console errors.
Corbuk
Corbuk
["hello", "corbuk"]
Please let me know if this is the expected output?
I'm new to React and its processes. How would I add a ref to an element as a prop? So far I am getting null for ref, and undefined for wordRef.
render(){
const {
str = "Functional components cannot leverage on the performance improvements and render optimizations that come with React.",
func = function(){
const words = str.split(" ");
const els = words.map( word => {
const ref = React.createRef();
console.log('ref',ref.current); // null
return React.createElement( "wrd", {attr:"ref-"+ref}, word+" ");
});
return els;
},
wordRef = React.createRef()
} = this.props;
You have some ways in order to create a ref. This is one:
Class components:
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
return (
<input
type="text"
ref={this.textInput} />
);
}
}
If you want to pass this ref as prop, you need to create the child component as class.
Function Components:
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
let textInput = React.createRef();
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
Documentation is useful and you can see how to create a ref properly: https://reactjs.org/docs/refs-and-the-dom.html https://reactjs.org/docs/forwarding-refs.html
However, take care using ref: "Avoid using refs for anything that can be done declaratively."
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
Seems like there's a lot of wrong ways of doing this and I'm fairly certain I'm trying to do this the wrong way (note this code doesn't work currently):
class SubmitLink extends React.Component<SubmitLinkProps, {}>{
constructor(props: SubmitLinkProps) {
super(props);
this.urlToPass = "nothing";
}
urlToPass: string;
handleChange(e: React.FormEvent<HTMLInputElement>) {
this.urlToPass = e.currentTarget.value;
}
public render() {
return <div>
<div>hello world {this.props.url}</div>
<input onChange={this.handleChange} type='text'></input>
<button onClick={() => {
this.props.submitlink(this.urlToPass);
}}>submit</button>
</div>
}
}
Besides the fact the code doesn't work (urlToPass is undefined at runtime, unsure why) i tlooks like a tonne of work just to grab an input from a textfield. At the same time this is the only way I could find googling on how to do it but it really doesn't feel correct.
The issue here is that the element contains its own state while React components also have their own internal state. The best way to handle this is to make the React component state the source of truth. You can read more about this best practice here: https://facebook.github.io/react/docs/forms.html
In your case, it would be to do the following:
class SubmitLink extends React.Component<SubmitLinkProps, {}>{
constructor(props: SubmitLinkProps) {
super(props);
this.state = { urlToPass: '' }
this.handleChange = this.handleChange.bind(this)
}
handleChange(e: React.FormEvent<HTMLInputElement>) {
this.setState({urlToPass: e.currentTarget.value});
}
public render() {
return <div>
<div>hello world {this.props.url}</div>
<input value={this.state.urlToPass} onChange={this.handleChange} type='text'></input>
<button onClick={() => {
this.props.submitlink(this.state.urlToPass);
}}>submit</button>
</div>
}
}
You should bind the handleChange method in your constructor.
this.handleChange = this.handleChange.bind(this);