Cannot read property 'state' of undefined in API call - reactjs

I have the following component in react
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cityName: "",
weather: ""
}
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
fetch(`https://api.weatherapi.com/v1/current.json?key=.....&q=${this.state.cityName}&aqi=no`)
.then(response => response.json())
.then(json => {
this.setState({ weather: json});
});
}
handleChange(event) {
this.setState({cityName: event.target.value});
}
render() {
return (
<center>
<p><font color="bluesky" size="10">Weather</font></p>
<div class="card-body">
<input type="text" value={this.state.value} onChange={this.handleChange} placeholder="Type the name of city"></input>
<h3>{this.state.cityName}</h3>
</div>
<div>
<button className="btn btn-secondary btn-sm" onClick={this.componentDidMount}>Check weather</button>
</div>
</center>
);
}
}
and for this line of code
fetch(`https://api.weatherapi.com/v1/current.json?key=...&q=${this.state.cityName}&aqi=no`)
I get the next error "TypeError: Cannot read property 'state' of undefined". I am new to react, can somebody help me to fix this error ?

You are calling component lifecycle method to handle form submit. Create a submitHandler just as you've the changeHandler and it should work fine.
onSubmit = () => {
fetch(`https://api.weatherapi.com/v1/current.json?key=.....&q=${this.state.cityName}&aqi=no`)
.then(response => response.json())
.then(json => {
this.setState({ weather: json});
});
}
The button will be configured as:
<button className="btn btn-secondary btn-sm" onClick={this.onSubmit}>Check weather</button>
Pro tip: Use arrow functions to define custom methods in your component.
Note: You need to configure the API key and handle the response correctly,use console.log(json) in the second .then() and see what you get.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cityName: "",
weather: ""
}
}
onSubmit = () => {
fetch(`https://api.weatherapi.com/v1/current.json?key=.....&q=${this.state.cityName}&aqi=no`)
.then(response => response.json())
.then(json => {
this.setState({ weather: json});
});
}
handleChange = (event) => {
this.setState({cityName: event.target.value});
}
render() {
return (
<center>
<p><font color="bluesky" size="10">Weather</font></p>
<div class="card-body">
<input type="text" value={this.state.cityName} onChange={this.handleChange} placeholder="Type the name of city"></input>
<h3>{this.state.weather}</h3>
</div>
<div>
<button className="btn btn-secondary btn-sm" onClick={this.onSubmit}>Check weather</button>
</div>
</center>
);
}
}

componentDidMount() is a function where its code is executed when the component is mounted, so when you fetch data with in componentDidMount(), the ${this.state.cityName} in the URL is still equal to nothing (cityName: "" in your default state)
You need to set a default value to cityName like
this.state = {
cityName: "London",
weather: ""
}
or
You need to fetch only after the user pressed a button
search() {
// fetch here
}
<button className="btn btn-secondary btn-sm" onClick={this.search}>Check weather</button>
And by the way you need to remove onClick={this.componentDidMount} from the button because componentDidMount is automatically called when the component is mounting, better replace this by onClick={this.search} and create a search function where you can fetch datas

Related

Why is React state not changing simultaneously with input change for a controlled component

I don't think I missed anything in making the form a controlled component. Why doesn't the state doesn't change as characters are being input?
class AddChores extends Component {
state = {
chore: "",
};
handleChange = (evt) => {
this.setState({ chore: evt.target.value });
};
handleSubmit = (evt) => {
evt.preventDefault();
this.props.addChores(this.state);
this.setState({ chore: "" });
};
render() {
console.log(this.state);
return (
<div>
<form onClick={this.handleSubmit}>
<input
type="text"
placeholder="New Chore"
value={this.state.chore}
onChange={this.handleChange}
/>
<button className="button">ADD CHORE</button>
</form>
</div>
);
}
}[![React dev tool showing no simultaneous update][1]][1]
I made some changes as below and it works fine for me. Hope it helps you too.
class AddChores extends Component {
constructor(props) {
super(props);
this.state = {
chore: ""
};
}
handleChange = (event) => {
this.setState({
chore: event.target.value
});
};
handleSubmit = (evt) => {
evt.preventDefault();
// this.props.addChores(this.state);
this.setState({ chore: "" });
};
componentDidUpdate(){
console.log('the state', this.state.chore)
}
render() {
return (
<div>
<form onClick={this.handleSubmit}>
<input
type="text"
placeholder="New Chore"
value={this.state.chore}
onChange={this.handleChange}
/>
</form>
</div>
);
}
}
not sure why is this happening but try using the second form of setState
this.setState(() => ({ chore: evt.target.value}))
check this https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

React Loader - Trying to get a loader when api request is made and to stop it when response is fetched

What I want is that, when I click on search button, then a loader/spinner should appear on screen until the data is fetched, when the data is fetched it should disappear.
Container.jsx
import React from 'react';
import './container.css'
import Weather from './weather';
var Loader = require('react-loader');
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
location: "",
weather: [],
loaded:false
};
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
componentDidMount() {
this.setState.loaded=false;
}
continue = (e) => {
this.setState({loaded:true});
const { location } = this.state;
const rawurl = 'http://api.weatherstack.com/current?access_key=d8fefab56305f5a343b0eab4f837fec1&query=' + location;
const url = rawurl;
e.preventDefault();
if (location.length < 1) {
return alert('Enter the details');
}
else {
fetch(url)
.then(response => response.json())
.then(data =>{
this.setState({weather:[data],loaded:false});
})
.catch(err => console.log("error ",err))
}
};
render() {
console.log(this.state.weather);
const weather =
this.state.weather.length> 0 ?
this.state.weather.map(item => (<Weather location={item.location.name} temperature={item.current.temperature} weather={item.current.weather_descriptions[0]} windSpeed={item.current.wind_speed} windDegree={item.current.wind_degree} windDir={item.current.wind_dir} humidity={item.current.humidity} visibility={item.current.visibility} />
))
:<span></span>
return (
<div id="container">
<div class="searchicon">
<input type="search" placeholder="Enter City !!" type="text" name="location" value={this.state.location} onChange={this.handleChange}></input>
<label class="icon">
<button onClick={this.continue} id="btn"><span class="fa fa-search"></span></button>
</label>
</div>
<div>
<Loader loaded={this.state.loaded}>
{weather}
</Loader>
</div>
</div>
);
}
}
export default Container;
What I am using here is react-loader
But right now,its not happening in the way I want, sometime before clicking the serach button it appears and when data is fetched it stops, i want to start it when the api req is made after click on search button and to stop when data is fetched.
first of all you should in the setState after fetching the data to make
this.setState({weather:[data],loaded:true});
second there's another way to do it you can separate the code in the return function like
{ !this.state.loaded ? <Loader loaded={false} options={options} className="spinner" />:{weather}}
as per the Doc in npm you can check it react-loader

Why is my unique ID posts after refresh browser (React, MongoDB, Express, Node)?

I am rather new to React and am making an app with the MERN stack to create, read, update and delete recipes but I'm getting the warning from React that I don't have a unique key for my recipe items. However, when I refresh my browser the warning goes away and my recipe object now has the id. It looks like the recipe ID is not being posted until after the recipe items are re-rendered. I don't get the warning if I pass the index as the key but I am just really wanting to understand why I keep getting this error when trying to use the ID generated from MongoDB.
class RecipeContiner extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
ingredients: "",
summary: "",
recipes: []
}
}
//GET RECIPES
componentDidMount() {
const url = 'http://localhost:5000/recipes/';
axios.get(url)
.then((res) => {
this.setState({ recipes: res.data })
}).catch(err => {
console.log(err);
});
}
onChangeHandler = (e) => {
this.setState({ [e.target.name]:e.target.value})
}
//POST RECIPE
onSubmitHandler = (e) => {
e.preventDefault();
const recipe = {
title: this.state.title,
ingredients: this.state.ingredients,
summary: this.state.summary
}
const url = 'http://localhost:5000/recipes/add';
axios.post(url, recipe)
.then(res => console.log('new recipe!', res.data));
this.setState({
recipes: [...this.state.recipes, recipe],
});
e.target.reset();
}
render() {
return (
<div>
<form onSubmit={this.onSubmitHandler}>
<label>Title:</label>
<input type="text" onChange={this.onChangeHandler} name="title"/>
<label>Ingredients:</label>
<input type="text" onChange={this.onChangeHandler} name="ingredients"/>
<label>Summary:</label>
<input type="text" onChange={this.onChangeHandler} name="summary"/>
<input type="submit" value="Submit" />
</form>
<RecipeList recipes={this.state.recipes} />
<Fab color="primary" aria-label="add">
<AddIcon />
</Fab>
</div>
);
}
//RECIPE LIST COMPONENT
const RecipeList = (props) => {
console.log('props.recipes', props.recipes)
const recipes = props.recipes;
return (
<div>
<ul>
{recipes.map((recipe, index) => (
<RecipeItem
key={recipe._id}
title={recipe.title}
ingredients={recipe.ingredients}
summary={recipe.summary}
/>
))}
</ul>
</div>
);
}
//RECIPE ITEM COMPONENT
const RecipeItem = (props) => {
return (
<li>
<div>{props.title}</div>
<div>{props.ingredients}</div>
<div>{props.summary}</div>
</li>
)
}
}```
[1]: https://i.stack.imgur.com/aZtEO.png
your state don't get the id after you post id. you just add the new recipe from the client and not form the server with the id.
axios.post(url, recipe)
.then(res => this.setState({
recipes: [...this.state.recipes, res.data],
} ,()=>console.log('new recipe!', res.data)));
will do the trick.

setState called from within functional argument fails to cause render

*edit to provide solution in comments
I have an app that renders 2 components, a SearchBar form and a Table of data. After the app mounts, an api call is made and setState is called, which triggers a render of the Table. It works fine.
The problem comes from the SearchBar component. On submission, the functional argument handleSubmit is called to make an api request and set the new state. SetState should trigger a render but it doesn't. The state is verified and accurate but there is no render.
Here is my code.
App.js
class App extends Component {
constructor(props) {
console.log('app constructor')
super(props)
this.state = {
items: [],
}
}
render() {
console.log('app render')
return (
<div>
<SearchBar onSubmit={this.handleSubmit} />
<Table data={this.state.items} />
</div>
)
}
componentDidMount() {
console.log('app mounted')
fetch('/api/items/?search=initial search')
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
console.log('state post mount ' + this.state.items.length)
})
}
handleSubmit(e) {
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
console.log('state post submit ' + this.state.items[0].name)
})
}
}
SearchBar.js
export default class SearchBar extends Component {
constructor(props) {
console.log('search bar constructor')
super(props)
this.onChange = this.handleChange.bind(this)
this.onSubmit = this.props.onSubmit.bind(this)
this.state = {
value: ''
}
}
handleChange(e) {
console.log('search bar changed ' + e.target.value)
this.setState({
searchBarValue: e.target.value
})
}
render() {
return (
<form className='form' id='searchForm' onSubmit={this.onSubmit}>
<input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
</form>
)
}
}
Table.js
export default class Table extends Component {
render() {
if (this.props.data.length === 0) {
return (
<p>Nothing to show</p>
)
} else {
return (
<div className="column">
<h2 className="subtitle">
Showing <strong>{this.props.data.length} items</strong>
</h2>
<table className="table is-striped">
<thead>
<tr>
{Object.entries(this.props.data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{this.props.data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</table>
</div>
)
}
}
}
Please set this in a variable, when function initiate:-
handleSubmit(e) {
let formthis=this;
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
formthis.setState({
items: data
})
console.log('state post submit ' + formthis.state.items[0].name)
})
}
AS I said in the comment, Remove this line this.onSubmit = this.props.onSubmit.bind(this) from the SearchBar component and replace this one
<form className='form' id='searchForm' onSubmit={this.onSubmit}>
with
<form className='form' id='searchForm' onSubmit={this.props.onSubmit}>
The problem is when you call bind the onSubmit from the props with the this as you did it is using the context of the SearchBar and not the parent so it sets the response to the state of the Search bar and not the App component which you want that way your items state of the parent component never changes an as such you don't get a re-render
Here is the relevant code for my solution. As harisu suggested, I changed the declaration of the form component. I also added a bind statement in the constructor of the parent.
App.js
class App extends Component {
constructor(props) {
console.log('app constructor')
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.state = {
items: [],
}
}
handleSubmit(e) {
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
})
console.log('state post submit ' + this.state.items[0].name)
}
}
SearchBar.js
export default class SearchBar extends Component {
render() {
return (
<form className='form' id='searchForm' onSubmit={this.props.onSubmit}>
<input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
</form>
)
}
}

SetState in React ES6

I'm just learning React & I just can't seem to get setstate in the componentdidmount function to work. It would be adorable if you could help me out. I already tried to bind it.
I keep getting errors such as Cannot read property 'setState' of undefined.
class ShareEvent extends React.Component {
constructor(props) {
super(props);
this.state = {copied: false};
this.componentDidMount = this.componentDidMount.bind(this);
}
componentDidMount() {
var clipboard = new Clipboard('#copy-button');
clipboard.on('success', function (e) {
this.setState({copied: true});
e.clearSelection();
});
clipboard.on('error', function (e) {
document.getElementById("title").innerHTML = 'Please copy manually.';
});
}
handleChange(event) {
event.preventDefault();
event.target.select();
}
render() {
const EventURL = GenerateEventUrl(this.props.EventName,this.props.EventTimeUTC);
return (
<div>
<h1>{this.state.copied ? "Copied!" : "Nicely done." }</h1>
<p>Now, simply share the link below.<br />It will display{' '}
<a href={EventURL}>the event</a>{' '}
in the local time of whoever visits it.</p>
<form>
<div className="input-group">
<input onClick={this.handleChange} type="text" className="form-control" defaultValue={EventURL} readOnly id="copy-input" />
<span className="input-group-btn">
<button className="btn btn-default" type="button" id="copy-button" data-clipboard-target="#copy-input" title="Copy to Clipboard">
Copy
</button>
</span>
</div>
</form>
</div>
);
}
}
You need to bind the this that references your component to your function. Change
function (e) {
this.setState({copied: true});
e.clearSelection();
}
to
function (e) {
this.setState({copied: true});
e.clearSelection();
}.bind(this)
or use ES6 arrow functions, which automatically bind this
e => {
this.setState({copied: true});
e.clearSelection();
}

Resources