this.state is undefined inside child component - javascript

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>;
}
}

Related

How to pass onChange input value to parent component in React js

I am trying to pass onChange input value from child component to parent component in react js. I pass with props. But in the component, it writes value as location: <input />. As I understand it return value as object but when I try to convert with Json.stringfy it returns an error. So how can pass and set this value in parent component?
class Search extends Component {
// Define Constructor
constructor(props) {
super(props);
render() {
return (
<div>
<Script url="https://maps.googleapis.com/maps/api/jskey=Key&libraries=places"
onLoad={this.handleScriptLoad}
/>
<input onChange={(e)=>this.props.handleChangeSearch(e)} defaultValue={this.props.location} id="autocomplete" placeholder="search city..."
style={{
margin: '0 auto',
maxWidth: 800,
}}
/>
</div>
);
}
}
export default Search;
Main Component
class MainPage extends Component {
constructor(props) {
super(props);
this.state = {
location: ""
}
}
componentDidMount(){
this.callLoc();
}
handleChangeSearch=(event)=> {
// console.log(JSON.stringify(event.target)+" event");
this.setState({location: event.target});
}
render() {
return (
<div id="main">
<Script url="https://maps.googleapis.com/maps/api/js?key=your_api_key&libraries=places"
onLoad={this.handleScriptLoad} />
<h3 id="mainText">Choose Your Next Destination</h3>
<div id='card'>
<div class="input-group">
<Search handleChangeSearch={this.handleChangeSearch} location={this.state.location}/>
<Button onClick={this.searchLoc}>Search</Button>
</div>
<br></br>
<Button onClick={()=>this.callLoc()} block>Near by Guides</Button>
</div>
</div>
);
}
event.target will just point to the element that generated this event, you need to use event.target.value to get the value of the input.
React uses synthetic events (or read here), so passing the event object can lead to a stale object.
// Pass the value and not the event object
<input onChange={ (e) => this.props.handleChangeSearch(e.target.value) } />
// Fix the handler
handleChangeSearch = (value) => { ... }

React: 2 way binding props

How can I do a 2 way binding of a variable from the parent component (Form.js), such that changes occurred in the child component (InputText.js) will be updated in the parent component?
Expected result: typing values in the input in InputText.js will update the state of Form.js
In Form.js
render() {
return (
<div>
<InputText
title="Email"
data={this.state.formInputs.email}
/>
<div>
Value: {this.state.formInputs.email} // <-- this no change
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
this.setState({
data: e.target.value
});
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.data} onChange={this.handleKeyChange} /> // <-- type something here
value: ({this.state.data}) // <-- this changed
</div>
)
}
}
You can manage state in the parent component itself instead of managing that on child like this (lifting state up):
In Form.js
constructor(props) {
super(props);
this.handleKeyChange = this.keyUpHandler.bind(this);
}
keyUpHandler(e) {
const { formInputs } = this.state;
formInputs.email = e.target.value
this.setState({
formInputs: formInputs
});
}
render() {
// destructuring
const { email } = this.state.formInputs;
return (
<div>
<InputText
title="Email"
data={email}
changed={this.handleKeyChange}
/>
<div>
Value: {email}
</div>
</div>
)
}
In InputText.js
export default class InputText extends React.Component {
render() {
// destructuring
const { title, data, changed } = this.props;
return (
<div>
<label className="label">{title}</label>
<input type="text" value={data} onChange={changed} />
value: ({data})
</div>
)
}
}
You can also make your InputText.js a functional component instead of class based component as it is stateless now.
Update: (How to reuse the handler method)
You can add another argument to the function which would return the attribute name like this:
keyUpHandler(e, attribute) {
const { formInputs } = this.state;
formInputs[attribute] = e.target.value
this.setState({
formInputs: formInputs
});
}
And from your from you can send it like this:
<input type="text" value={data} onChange={ (event) => changed(event, 'email') } />
This assumes that you have different inputs for each form input or else you can pass that attribute name also from parent in props to the child and use it accordingly.
You would need to lift state up to the parent
parent class would look something like
onChangeHandler(e) {
this.setState({
inputValue: e.target.value // you receive the value from the child to the parent here
})
}
render() {
return (
<div>
<InputText
title="Email"
onChange={this.onChangeHandler}
value={this.state.inputValue}
/>
<div>
Value: {this.state.inputValue}
</div>
</div>
)
}
children class would look something like
export default class InputText extends React.Component {
constructor(props) {
super(props);
this.state = props;
}
render() {
return (
<div>
<label className="label">{this.state.title}</label>
<input type="text" value={this.state.value} onChange={this.props.onChange} />
value: ({this.state.value})
</div>
)
}
}
You can simply pass a callback from Form.js to InputText and then call that callback in InputText on handleKeyChange

Stop Relay: Query Renderer in reloading data for certain setStates

I'm currently following this and I did get it to work. But I would like to know if there is a way to stop the Query Render from reloading the data when calling this.setState(). Basically what I want is when I type into the textbox, I don't want to reload the data just yet but due to rendering issues, I need to set the state. I want the data to be reloaded ONLY when a button is clicked but the data will be based on the textbox value.
What I tried is separating the textbox value state from the actual variable passed to graphql, but it seems that regardless of variable change the Query will reload.
Here is the code FYR.
const query = graphql`
query TestComponentQuery($accountId: Int) {
viewer {
userWithAccount(accountId: $accountId) {
name
}
}
}
`;
class TestComponent extends React.Component{
constructor(props){
super(props);
this.state = {
accountId:14,
textboxValue: 14
}
}
onChange (event){
this.setState({textboxValue:event.target.value})
}
render () {
return (
<div>
<input type="text" onChange={this.onChange.bind(this)}/>
<QueryRenderer
environment={environment}
query={query}
variables={{
accountId: this.state.accountId,
}}
render={({ error, props }) => {
if (error) {
return (
<center>Error</center>
);
} else if (props) {
const { userWithAccount } = props.viewer;
console.log(userWithAccount)
return (
<ul>
{
userWithAccount.map(({name}) => (<li>{name}</li>))
}
</ul>
);
}
return (
<div>Loading</div>
);
}}
/>
</div>
);
}
}
Okay so my last answer didn't work as intended, so I thought I would create an entirely new example to demonstrate what I am talking about. Simply, the goal here is to have a child component within a parent component that only re-renders when it receives NEW props. Note, I have made use of the component lifecycle method shouldComponentUpdate() to prevent the Child component from re-rendering unless there is a change to the prop. Hope this helps with your problem.
class Child extends React.Component {
shouldComponentUpdate(nextProps) {
if (nextProps.id === this.props.id) {
return false
} else {
return true
}
}
componentDidUpdate() {
console.log("Child component updated")
}
render() {
return (
<div>
{`Current child ID prop: ${this.props.id}`}
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
id: 14,
text: 15
}
}
onChange = (event) => {
this.setState({ text: event.target.value })
}
onClick = () => {
this.setState({ id: this.state.text })
}
render() {
return (
<div>
<input type='text' onChange={this.onChange} />
<button onClick={this.onClick}>Change ID</button>
<Child id={this.state.id} />
</div>
)
}
}
function App() {
return (
<div className="App">
<Parent />
</div>
);
}

How to increment a value in one sibling component from another on click?

function LeagueBadge(props) {
return (
<img src={props.badgeUrl} alt="missing alt text" />
);
}
class LeagueInfo extends Component {
constructor(props) {
super(props);
this.state = {
amountOfPlayers: null,
rpPerSecond: null,
rpCost: null,
};
}
render() {
return (
<div>
<h4>{this.props.name} players: {this.props.amountOfPlayers}</h4>
<h4>RP per second: {this.props.rpPerSecond}</h4>
<h4>RP cost: {this.props.rpCost}</h4>
</div>
);
}
}
class League extends Component {
render() {
return (
<div>
<LeagueBadge badgeUrl={this.props.badge} />
<LeagueInfo name={this.props.name} />
</div>
);
}
}
class App extends Component {
render() {
return (
<div className="App">
<h1>Players</h1>
<League name="Bronze" badge={ require('./bronze.png') }></League>
<League name="Silver" badge={ require('./silver.png') }></League>
<League name="Gold" badge={ require('./gold.png') }></League>
<League name="Platinum" badge={ require('./platinum.png') }></League>
<League name="Diamond" badge={ require('./diamond.png') }></League>
<League name="Master" badge={ require('./master.png') }></League>
<League name="Challenger" badge={ require('./challenger.png') }></League>
</div>
);
}
}
I want to be able to click at the image which is the LeagueBadge component and increment the value of amountOfPlayers in their sibling LeagueInfo. I've googled react siblings communication already and only found examples with input tag and onChange but here I want img tag or button and onClick.
You could lift the state for amountOfPlayers up into your <Leauge /> component, so that:
- updates can be triggered from <LeagueBadge />
- and, that state can be passed down to your <LeagueInfo /> component
This would allow you to share and synchronize state between the <LeagueInfo /> and <LeagueBadge /> siblings as you require.
To do this, you would need to add an onClick callback to <LeagueBadge /> which is fired when the img element is clicked. In the <Leauge /> render method, you could provide the logic that increments the amountOfPlayers state in <Leauge />. When the amountOfPlayers is incremented, and setState is called (in <Leauge />), this would in turn cause your <Leauge /> component to re-render itself (and children/siblings). Because the updated value for amountOfPlayers is passed as a prop to <LeagueInfo /> component, this updated value would be rendered in <LeagueInfo />- effectively achieving what you're after.
Something like this might work for you:
class LeagueBadge extends Component {
render() {
// Add props.onClick callback to trigger click event in <League />
// component
return (
<img src={this.props.badgeUrl} alt="missing alt text"
onClick={() => this.props.onClick()} />
);
}
}
class LeagueInfo extends Component {
constructor(props) {
super(props);
this.state = {
// amountOfPlayers: null, // This is not needed, as it's supplied by props
rpPerSecond: null,
rpCost: null,
};
}
render() {
return (
<div>
<h4>{this.props.name} players: {this.props.amountOfPlayers}</h4>
<h4>RP per second: {this.props.rpPerSecond}</h4>
<h4>RP cost: {this.props.rpCost}</h4>
</div>
);
}
}
class League extends Component {
componentWillMount() {
this.setState({
amountOfPlayers : 0
})
}
render() {
// Locally defined function that increments amountOfPlayers and
// updates state
const incrementAmountOfPlayers = () => {
this.setState({ amountOfPlayers :
this.state.amountOfPlayers + 1 })
}
return (
<div>
<LeagueBadge badgeUrl={this.props.badge}
onClick={ () => incrementAmountOfPlayers() } />
<LeagueInfo name={this.props.name} amountOfPlayers={ this.state.amountOfPlayers } />
</div>
);
}
}
Keep your state in the league component and pass the function responsible to change it to LeagueBadge like:
class League extends Component {
constructor(props) {
super(props);
this.state = {
amountOfPlayers: null,
};
}
handleClick = () => {
this.setState(prevState => {
return {amountOfPlayers: prevState.amountOfPlayers + 1}
})
}
render() {
return (
<div>
<LeagueBadge badgeUrl={this.props.badge} incrementPlayers={this.handleClick}/>
<LeagueInfo name={this.props.name} amountOfPlayers={this.state.amountOfPlayers}/>
</div>
);
}
}
function LeagueBadge(props) {
return (
<img src={props.badgeUrl} alt="missing alt text" onClick={this.props.incrementPlayers}/>
);
}
use this.props.amountOfPlayers in your Info component.

Change parent state inside react child on input change

I have the following parent component:
class NewPetForm extends React.Component {
state = {
name: '',
age: '',
animal: '',
breed: ''
};
render() {
return (
<StyledNewPetForm >
<Input label="name" />
<Input label="age" />
<Input label="animal"/>
<Input label="breed"/>
<Button type="submit" />
</StyledNewPetForm>
);
}
}
And the following child component:
class Input extends React.Component {
render() {
return (
<StyledWrapper>
<StyledInput
value={this.props.test}
type="text"
/>
</StyledWrapper>
);
}
}
Is it possible to listen to changes in all inputs in children components and update the state accordingly?
What I want to achieve is basically passing handlers to different Inputs and update state dynamically, so there is a possibility to reuse Input component.
changeHandler = (event,stateName) =>{
this.setState({[stateName]:event.target.value]})
}
<Input changed={changeHandler(event,'name')}></Input>
<Input changed={changeHandler(event,'age')}></Input>
<Input changed={changeHandler(event,'breed')}></Input>
// Inside Input
<input onChange={this.props.changed}/>
Thanks for help!
Yes you can do that, you can pass a function to the child element as a property and call it when a change occurs in the child.
Here is an example:
class NewPetForm extends React.Component {
state = {
name: ''
};
onValueChange(key, event) {
this.setState({[key]: event.target.value})
}
render() {
return (
<StyledNewPetForm >
<Input value={this.state.name} onValueChange={this.onValueChange.bind(this, 'name')} />
</StyledNewPetForm>
);
}
}
and the child
class Input extends React.Component {
render() {
return (
<StyledWrapper>
<StyledInput
onChange={this.props.onValueChange}
value={this.props.value}
type="text"
/>
</StyledWrapper>
);
}
}
{[key]: event.target.value} may seem confusing, is just the new syntax that lets you use a string as a property name in an object literal.
The important part is onChange={this.props.onValueChange}, here, I'm calling the parent function NewPetForm.onValueChange when the input's value changes.

Categories