I am trying to count the remaining character using reactjs. I have defined the function which talks with the state and is passed down to the child component. In the child component i am getting an error of Uncaught TypeError: Cannot read property 'length' of undefined .
app.js
export default class App extends Component {
constructor(props){
super(props);
this.state = {max_char:32};
this.handleChange = this.handleChange.bind(this);
}
handleChange(charLength){
console.log('charLength');
this.setState({
max_char:32 - charLength.length
});
console.log(this.state.max_char);
}
render() {
return (
<div>
<Layout fixedHeader>
<Content>
<div className="page-content">
<DeviceEventList />
<DeviceDialog onChange={this.handleChange} />
</div>
<div className="empty-space">
</div>
</Content>
</Layout>
</div>
);
}
}
device-dialog.js
class DeviceDialog extends Component {
constructor(props) {
super(props);
console.log('this.props',this.props.onChange);
}
handleInputChange(event){
console.log(event.target.value);
let name = event.target.value;
this.props.onChange(name);
}
renderOwnADevice(){
console.log('open',this.props.active_device_event.open);
return(
<div className="device-action">
<Dialog open={this.props.active_device_event.open} onCancel={this.props.CancelDeviceEvent}>
<DialogContent>
<Textfield
onChange={()=> {this.handleInputChange(event)}}
pattern="-?[0-9]*(\.[0-9]+)?"
error="Input is not a number!"
label="Device Serial Number"
floatingLabel
/>
<span style={{ float:'right'}}>character</span>
</DialogContent>
</Dialog>
</div>
)
}
render() {
if ( !this.props.active_device_event){
return <h5 style={{ textAlign:'center' }}>Click icon based on your work</h5>;
}
let icon_name = this.props.active_device_event.icon_name;
if( icon_name == 'devices_other'){
return (<div>Device Other</div>);
}
if( icon_name == 'add_circle_outline'){
return (this.renderOwnADevice());
}
}
}
I would guess onChange={()=> {this.handleInputChange(event)}} should be onChange={(event) => {this.handleInputChange(event)}}. Now you're passing an event variable that's not defined.
As an aside: probably also better to bind the handler in the constructor like you did in app.js instead of having an anonymous function wrapper in your render.
event is never defined in this line: onChange={()=> {this.handleInputChange(event)}}. Therefore your handleChange function is receiving an undefined value, not a string.
<Textfield
onChange={this.handleInputChange}
pattern="-?[0-9]*(\.[0-9]+)?"
error="Input is not a number!"
label="Device Serial Number"
floatingLabel
/>
Your this.handleInputChange will now properly be passed the event argument.
Related
I am getting the error: Uncaught TypeError: Cannot read properties of undefined (reading 'state') even though state is defined in the constructor of my React component. I get the error at the line where I set the value of the <input> to {this.state.deckName}
export class DeckForm extends React.Component {
constructor(props) {
super(props);
this.state = {
deckName: '',
deckList: ''
};
// Bind our event handler methods to this class
this.handleDeckNameChange = this.handleDeckNameChange.bind(this);
this.handleDeckListChange = this.handleDeckListChange.bind(this);
this.handleSubmission = this.handleSubmission.bind(this);
}
// Event handler method to update the state of the deckName each time a user types into the input form element
handleDeckNameChange(event) {
let typed = event.target.value;
this.setState({ deckName: typed });
}
// Event handler method to update the state of the deckList each time a user types into the textarea from element]
handleDeckListChange(event) {
let typed = event.target.value;
this.setState({ deckList: typed });
}
// Event handler method to handle validation of deckName and deckList
handleSubmission(event) {
console.log(`${this.state.deckName}`);
console.log(`${this.state.deckList}`)
}
render() {
return (
<form className='was-validated'>
<this.DeckName />
<this.DeckList />
<button type='submit' className='btn-lg btn-warning mt-3'>Create</button>
</form>
);
}
DeckName() {
return (
<div className='form-group mb-3'>
<input
value={this.state.deckName} /* ERROR HERE */
onChange={this.handleDeckNameChange}
type='text'
placeholder='Deck name'
className='form-control'
required
/>
</div>
);
}
DeckList() {
let format = 'EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3'
return (
<div className='form-group'>
<textarea
value={this.state.deckList}
onChange={this.handleDeckListChange}
className='form-control'
rows='15'
required
>
{format}
</textarea>
</div>
);
}
}
Use below code it's working for me
https://codesandbox.io/s/weathered-water-jm1ydv?file=/src/App.js
DeckName() {
return (
<div className="form-group mb-3">
<input
value={this?.state.deckName} /* ERROR HERE */
onChange={this?.handleDeckNameChange}
type="text"
placeholder="Deck name"
className="form-control"
required
/>
</div>
);
}
DeckList() {
let format =
"EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3";
return (
<div className="form-group">
<textarea
value={this?.state.deckList}
onChange={this?.handleDeckListChange}
className="form-control"
rows="15"
required
>
{format}
</textarea>
</div>
);
}
You can use es6 function to return components which exist outside of parent rather than using method,change only this part of code:
1.instead of DeckName(){...}use DeckName =()=>{....}
2.instead of DeckList(){...}use DeckList =()=>{....}
Full modified code:
import React, { Component } from "react";
export class DeckForm extends Component {
constructor(props) {
super(props);
this.state = { deckName: "", deckList: "" };
// Bind our event handler methods to this class
this.handleDeckNameChange = this.handleDeckNameChange.bind(this);
this.handleDeckListChange = this.handleDeckListChange.bind(this);
this.handleSubmission = this.handleSubmission.bind(this);
}
// Event handler method to update the state of the deckName each time a user types into the input form element
handleDeckNameChange(event) {
let typed = event.target.value;
this.setState({ deckName: typed });
}
// Event handler method to update the state of the deckList each time a user types into the textarea from element]
handleDeckListChange(event) {
let typed = event.target.value;
console.log(typed);
this.setState({ deckList: typed });
}
// Event handler method to handle validation of deckName and deckList
handleSubmission(event) {
console.log(`${this.state.deckName}`);
console.log(`${this.state.deckList}`);
}
render() {
return (
<form className="was-validated">
<this.DeckName />
<this.DeckList />
<button type="submit" className="btn-lg btn-warning mt-3">
Create
</button>
</form>
);
}
DeckName = () => {
return (
<div className="form-group mb-3">
<input
value={this.state.deckName}
onChange={this.handleDeckNameChange}
type="text"
placeholder="Deck name"
className="form-control"
required
/>
</div>
);
};
DeckList = () => {
let format =
"EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3";
return (
<div className="form-group">
<textarea
value={this.state.deckList}
onChange={this.handleDeckListChange}
className="form-control"
rows="15"
required
>
{format}
</textarea>
</div>
);
};
}
Live Demo:
https://codesandbox.io/s/brave-hill-l0eknx?file=/src/DeckForm.js:0-2091
Another way to solve the issue is defining DeckName() method using arrow function. Here's a code snippet I tried with react 17.0.2 which worked perfectly fine for me.
It's always recommended to use arrow function to define methods in class based components, since arrow function inherit "this" from the block its called from, so you also don't have to do .bind(this) whenever you call methods.
JSX
import React, { Component } from 'react'
class Test extends Component {
constructor(props) {
super(props);
this.state = {
deckName: '',
deckList: ''
};
}
DeckName = () => {
return (
<div className='form-group mb-3'>
<input
value={this.state.deckName} /* ERROR HERE */
onChange={this.handleDeckNameChange}
type='text'
placeholder='Deck name'
className='form-control'
required
/>
</div>
);
}
render() {
return (
<form className='was-validated'>
<this.DeckName />
<button type='submit' className='btn-lg btn-warning mt-3'>Create</button>
</form>
);
}
}
export default Test
I am trying to :
generate radio buttons from a constant array using Map in react
let user select one and set the state with handleChange()
With the following code I was able to achieve 1, but for some reason when I try to display with handleChange() I see it is an empty string.
Could you please help me ?
Thanks
import React, { Component } from "react";
const members = ["Araki", "Ibata", "Fukutome", "Woods", "Alex", "Tatsunami"];
export default class MyRadio extends Component {
constructor(props) {
super(props);
this.state = {
lastName: "",
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
console.log("handleChange() e:" + e.target.value);
this.setState({
[e.target.name]: e.target.value,
});
}
render() {
console.log("render");
return (
<form>
<div>
{members.map((item) => (
<React.Fragment>
<label htmlFor={item.name}> {item}</label>
<input
name="lastName"
key={item.name}
id={item.name}
value={item.name}
type="radio"
onChange={this.handleChange}
/>
</React.Fragment>
))}
</div>
<div></div>
</form>
);
}
}
To make this workable solution you have to change the members as follow
const members = [{name: "Araki", name: "Ibata", ...}];
array should be a object array with each object has name property because in the map you are expecting name should be there as item.name.
Or either you have to change the loop without item.name you have to use item
{members.map((item) => (
<React.Fragment>
<label htmlFor={item}> {item}</label>
<input
name="lastName"
key={item}
id={item}
value={item}
type="radio"
onChange={this.handleChange}
/>
</React.Fragment>
))}
I want to render a child element based on the state in its parent. I tried to do the following (simplified version of the code):
class DeviceInfo extends Component {
constructor(props) {
super(props);
this.state = {
currentTab: "General",
};
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
navToggle(tab) {
this.setState({ currentTab: tab });
}
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
render() {
return (
<React.Fragment>
<div className="container">
<Nav className="nav-tabs ">
<NavItem>
<NavLink
className={this.state.currentTab === "General" ? "active" : ""}
onClick={() => {
this.navToggle("General");
}}
>
General
</NavLink>
</div>
{ this.tabsMap[this.state.currentTab] }
</React.Fragment>
);
}
}
But it did not work properly. Only when I put the contents of the tabsMap straight in the render function body it works (i.e. as a react element rather then accessing it through the object). What am I missing here?
Instead of making tabsMap an attribute which is only set when the component is constructed, make a method that returns the object, and call it from render:
getTabsMap() {
return {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
};
render() {
...
{ this.getTabsMap()[this.state.currentTab] }
...
}
You defining instance property with this.tabsMap (should be syntax error):
export default class App extends React.Component {
tabsMap = { General: <div>Hello</div> };
// Syntax error
// this.tabsMap = { General: <div>World</div> };
render() {
// depends on props
const tabsMapObj = {
General: <div>Hello with some props {this.props.someProp}</div>
};
return (
<FlexBox>
{this.tabsMap['General']}
{tabsMapObj['General']}
</FlexBox>
);
}
}
Edit after providing code:
Fix the bug in the constructor (Note, don't use constructor, it's error-prone, use class variables).
Moreover, remember that constructor runs once before the component mount if you want your component to be synchronized when properties are changed, move it to render function (or make a function like proposed).
class DeviceInfo extends Component {
constructor(props) {
...
// this.props.id not defined in this point
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={props.id}
/>
</React.Fragment>
}
render() {
// make a function to change the id
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
return (
<>
{ this.tabsMap[this.state.currentTab] }
</>
);
}
}
I think it's a this binding issue. Not sure if your tabsMap constant should have this in front of it.
Alternative answer... you can inline the expression directly in the render as
{ this.state.currentTab === 'General' && <GeneralCard id={this.props.id} /> }
In my code, I have the render() function. In this method this.state is available. If this.LoadingIcon just contains text, all is good:
public render() {
return <div>
<h1>Search company</h1>
<this.LoadingIcon />
<label value={this.state.query} />
<div className='form-group'>
<input type='text' className='form-control' value={this.state.query} onChange={this.handleChange.bind(this)} />
</div>
<button onClick={() => { this.searchVat() }}>Search</button>
</div>;
}
However, if I suddenly want to use the state and make the LoadingIcon conditional:
LoadingIcon(props: any) {
if (this.state.loading) {
return <h1>LOADING</h1>;
} else {
return <h1>NOT LOADING</h1>;
}
}
You get the:
TypeError: Cannot read property 'state' of undefined
Why is this? And how to solve?
There are two issues with your code:
this.state is undefined because LoadingIcon is a stateless component
in React, the parent's state is not directly available in the child component
To access the parent's state in the child, you need to pass the state as prop:
<this.LoadingIcon loading={this.state.loading} />
Then in your child component, you can use the props to retrieve the parent's state:
LoadingIcon(props: any) {
if (props.loading) {
return <h1>LOADING</h1>;
} else {
return <h1>NOT LOADING</h1>;
}
}
I have been playing around with reactjs, and building some basic forms all works good but then it became complicated when i tried to break into smaller compoments
i have a simple component that has a form in it called XMLParser, and it has 2 smaller components inside called XMLInput and XMLResult.
XMLResult is pretty straight forward it just passes the value to as the props, but couldnt figure out what is the best approact to use XMLInput i couldnt get the binding to work with the child component, thanks for any pointers.
function EppResult(props) {
const resultXML = props.xmlData;
return <div style={styles.child}>
<textarea style={styles.outputBox} name="resultXML" value={resultXML} readOnly/>
</div>;
}
class EppInput extends Component{
render(){
return <textarea
style={styles.inputBox}
placeholder="Enter XML here!"
name="xmlData"
value={this.props.value}
onChange={this.props.handleInputChange}
/>;
}
}
class XMLParser extends Component {
constructor(props) {
super(props);
this.state = {xmlData : ""};
this.state = {resultXML : ""};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log("do submit");
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<div style={styles.container}>
<form onSubmit={this.handleSubmit}>
<div style={styles.container}>
<div style={styles.child}>
<XMLInput value={this.state.xmlData} onChange={this.handleInputChange} />
</div>
<div style={styles.child}>
<div style={styles.formContainer}>
<button style={styles.formElement}>Run!</button>
</div>
</div>
<XMLResult xmlData={this.state.resultXML}/>
</div>
</form>
</div>
);
}
}
I'm seeing a number of problems:
EppInput is the sub-component name, but XMLInput is used in the main component
You pass an onChange prop, but refer to it as
this.props.handleInputChange -- it should be
this.props.onChange
Just a note--I don't know where the styles object is, but I'm going
to assume it's available to all the components
I haven't tested this, but here's a basic cleanup, with some alterations to see some other ways of doing things:
// stateless functional component
const XMLResult = ({ xmlData }) => (
<div style={styles.child}>
<textarea
style={styles.outputBox}
name="resultXML"
value={xmlData}
readOnly
/>
</div>
);
// stateless functional component
// props are passed directly to the child element using the spread operator
const XMLInput = (props) => (
<textarea
{...props}
style={styles.inputBox}
placeholder="Enter XML here!"
name="xmlData"
/>
);
class XMLParser extends Component {
constructor(props) {
super(props);
this.state = { xmlData: "", resultXML: "" };
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log("do submit");
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<div style={styles.container}>
<form onSubmit={this.handleSubmit}>
<div style={styles.container}>
<div style={styles.child}>
<XMLInput
value={this.state.xmlData}
onChange={this.handleInputChange}
/>
</div>
<div style={styles.child}>
<div style={styles.formContainer}>
<button style={styles.formElement}>Run!</button>
</div>
</div>
<XMLResult xmlData={this.state.resultXML} />
</div>
</form>
</div>
);
}
}