how to fix "Cannot read property 'options' of null" in this code? - reactjs

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?

Related

How to be able to open the file explorer in a class component and read the selected file in React JS

I am trying to open the file explorer based on a props condition.
However, due to a class Component I am not able use the useEffect hook.
Below is my code :
The class component :
class UploadFile extends React.Component {
constructor(props) {
super(props);
this.state = {
isClicked:0
};
this.inputFileRef = React.createRef();
this.onFileExplorerClick = this.onFileExplorerClick.bind(this);
}
onFileExplorerClick(fileRef) {
this.setState ({isClicked:1});
fileRef.current.click();
}
clickMe() {
console.log("this is click Me!");
}
render() {
const loadFile = this.props.loadFile;
return (
<div>
<Button onClick= {()=>this.clickMe()}>Click this </Button>
{ (loadFile &&
<form style={{ visibility: "collapse" }}>
<input type="file" ref={this.inputFileRef} />
<button onClick={this.onFileExplorerClick(this.inputFileRef)}></button>
</form>
) || ''}
</div>
);
}
}
If I am using a function component with useEffect the file explorer is popping up and working, but in class component , I am not able to bring it up and access the file uploaded.
The inputFileRef inside the onFileExplorer click is coming as undefined(or null), hence the click event (this.inpuFileRef.current.click()) is not happening. How can it be captured without actually clicking on a visible Choose File button ?
Please help.
Thanks.
I changed the render function and added life Cycle methods which could solve it.
class UploadFile extends React.Component {
constructor(props) {
super(props);
this.state = {
isClicked:0
};
this.inputFileRef = React.createRef();
this.onFileExplorerClick = this.onFileExplorerClick.bind(this);
}
componentDidMount() {
let loadFile = this.props.loadFile;
if(loadFile) this.setState ({isClicked:1});
}
componentDidUpdate() {
if(this.state.isClicked) this.onFileExplorerClick();
}
onFileExplorerClick() {
this.inputFileRef.current.click();
this.setState ({isClicked:0});
}
clickMe() {
console.log("this is click Me!");
}
render() {
const loadFile = this.props.loadFile;
return (
<div>
<Button onClick= {()=>this.clickMe()}>Click this </Button>
<input type="file" ref={this.inputFileRef} style:{{visibility:"collapse"}} />
</div>
);
}
}

Moving props data to state to generate forms

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.

this.state is null inside render React and Typescript

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

React JS - Prop undefined after refactoring to ES6

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/

How to handle onChange event that sets the state in React?

I am learning React and in the below code I get ...cannot update during an existing state transition.... While looking to fix it, I read in SO that setState should not be used within render() as it calls render() repeatedly resulting in infinite loop. But I dont know how to fix it.
import React from 'react';
import ReactDOM from 'react-dom';
export default class CheckBox extends React.Component{
constructor() {
super();
this.state = {isChecked: false};
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked});
}
render(){
var txt;
if (this.state.isChecked) {
txt = 'checked'
} else {
txt = 'unchecked'
}
return(
<div>
<input type="checkbox" onChange={this.handleChecked()}/>
<p>This box is {txt}</p>
</div>
);
}
}
ReactDOM.render(<CheckBox/>, document.getElementById('hello'));
You should pass to onChange reference to function but not call it., in your example you are calling handleChecked(because there is () after function) and result pass to onChange however result in this case will be undefined so onChange looks like onChange={ undefined }. Also, you can't set state during the initial render, but you are trying to do it with this.handleChecked() which contains this.setState.
export default class CheckBox extends React.Component{
constructor() {
super();
this.state = {isChecked: false};
// set this (with .bind),
// because you need get methods from CheckBox, like .setState
this.handleChecked = this.handleChecked.bind(this);
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked});
}
render(){
var txt;
if (this.state.isChecked) {
txt = 'checked'
} else {
txt = 'unchecked'
}
// remove () after handleChecked because you need pass
// reference to function
// also add return statement before <div>
return <div>
<input type="checkbox" onChange={ this.handleChecked }/>
<p>This box is {txt}</p>
</div>
}
}
Example

Resources