I am trying to fetch an api inside componentDidMount. The api result will be set to the component's state and the state mapped and passed to a children component.
If I fetch the api using the fetch method inside the componentDidMount everything works fine:
componentDidMount(){
fetch(apiToFetch)
.then((result) => result.json())
.then((result) => result.entries)
.then((entries) => this.setState({ ...this.state, entries }))
.catch((error) => console.log(error));
}
if I use fetch inside a method and then call this method inside componentDidMount nothing is rendered:
componentDidMount() {
this.fetchApiToEntries(GLOBAL_PORTFOLIO_COLLECTION_API);
}
fetchApiToEntries(apiToFetch) {
fetch(apiToFetch)
.then((result) => result.json())
.then((result) => result.entries)
.then((entries) => this.setState({ ...this.state, entries }))
.catch((error) => console.log(error));
}
I cannot understand what I am missing from the lifecycle.
Shouldn't react do the following?
Init the state
Render
Call componentDidMount
Rerender
Here is my initial component:
export default class Portfolio extends React.Component {
constructor(props) {
super(props);
this.state = {
entries: []
}
}
componentDidMount() {
this.fetchApiToEntries(GLOBAL_PORTFOLIO_COLLECTION_API);
}
fetchApiToEntries(apiToFetch) {
fetch(apiToFetch)
.then((result) => result.json())
.then((result) => result.entries)
.then((entries) => {
this.setState({
...this.state,
entries
})
})
.catch((error) => console.log(error));
}
render() {
return (
<Fade bottom>
<div className="Portfolio">
<div className="Portfolio__title"><h4 className="color--gradient text--spacing">WORKS</h4></div>
<OwlCarousel {...options}>
{this.state.entries.map((item) => (
<PortfolioElement item={item} />
))}
</OwlCarousel>
<AnchorLink href='#contact'><Button className="contact-button btn--gradient slider-button Services__button">Let's get in touch</Button></AnchorLink>
</div>
</Fade>
)
}
}
PortfolioElement is the actual component not being rendered.
Any advice?
Thank you.
Edit: both methods are not rerendering the component the right way (...something I didn't expect: I don't know why but if I call them twice in componentDidMount the component will render the right way). I think I am missing something in the state.
I have no error in the console and this is how I set my initial state:
this.state={entries:[]}
and this is what the actual entries looks like from the console:
entries:
[0:{credits: "..."
description: "..."
featuredImage: {path: "portfolio/01.jpg"}
firstImage: {path: "portfolio/firstimage.jpg"}
secondImage: []
slug: "..."
tasks: (5) [{…}, {…}, {…}, {…}, {…}]
title: "..."
_by: "5beae6553362340b040001ee"
_created: 1542123322
_id: "5beaef3a3362340bf70000b4"
_mby: "5beae6553362340b040001ee"
_modified: 1542149308
},
1:{...}
]
My state after the fetch is the same way.
UPDATE I figured out that the the problem is: when the state changes the component is not rerendering the child with the correct props. I called the API in an higher order component passed down the props and added a componentWillUpdate method forcing a state refresh that rerenders the component. Not the ideal solution but I am not figuring out other ways until now. Any advice?
Idk what your api response is but I tested your code with a fake API and changed
fetchApiToEntries(apiToFetch){}
to Arrow Function (Arrow Function)
fetchApiToEntries = (apiToFetch) => {}
and it's working fine.
Full Example:
export default class Portfolio extends React.Component {
constructor(props) {
super(props);
this.state = {
entries: []
}
}
componentDidMount() {
this.fetchApiToEntries('https://jsonplaceholder.typicode.com/posts');
}
fetchApiToEntries = (apiToFetch) => {
fetch(apiToFetch)
.then(result => result.json())
.then((entries) => {
this.setState({
...this.state,
entries
})
})
.catch((error) => console.log(error));
}
render() {
const {entries} = this.state;
console.log(entries);
return (
// Everything you want to render.
)
}
}
Hope it helps you.
do you need to bind fetchApiToEntries in the constructor or use fat arrows?
this.fetchApiToEntries = this.fetchApiToEntries.bind(this);
sorry I cant comment not enough rep
Related
I have React class component called SearchLocationForm.js which is a child of App.js. Inside of the SearchLocationForm.js I have this code below
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
error: null,
isLoaded: false,
coordinates: []
}
this.appid = this.props.appid;
}
handleSubmit(location) {
fetch(
`http://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${this.props.appid}`
)
.then(res => res.json())
.then(resp => this.setState({coordinates: [resp.lat, resp.lon]}))
.catch((error) => {
console.error("there has been an issue with te GeoCode API Call", error)
})
}
render() {
return (
<LocationInput className="searchLocationForm" handleSubmit={this.handleSubmit}/>
)
}
I am trying to figure out how I can use the setState() method to update the component state with the API response, and afterwards lift that up to the App.js component in order to make subsequent calls.
I have lookup many answers regarding this problem and have tried implementing many solutions but nothing seems to work as of now. What do you see going on within my code that seems to be causing the issue? Thanks!
If yiu want only the coordinate will update and other states will remains same you have to use spread operator like this way
handleSubmit(location) {
fetch(`http://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${this.props.appid}`)
.then(res => res.json())
.then(resp => this.setState({...thia.state, coordinates: [resp.lat, resp.lon]}))
.catch((error) => {
console.error("there has been an issue with te GeoCode API Call", error)
})
}
And if you want to lift the state up to the app.js, you have to define the state on app.js and set the state from this component. Read more about lifting state up from here
Give you the full example. Do it like this way.
class App extends Component {
state = {
error: null,
isLoaded: false,
coordinates: []
}
handleSubmit(location) {
fetch(`http://api.openweathermap.org/geo/1.0/direct?q=${location}&appid=${this.props.appid}`)
.then(res => res.json())
.then(resp => this.setState({...thia.state, coordinates: [resp.lat, resp.lon]}))
.catch((error) => {
console.error("there has been an issue with te GeoCode API Call", error)
})
}
render() {
return (
<div>
<Hello name={this.state.name} />
<p>
Start editing to see some magic happen :)
</p>
<SearchLocationForm state={this.state} handleSubmit={this.handleSubmit} />
</div>
);
}
}
class SearchLocationForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<LocationInput className="searchLocationForm" handleSubmit={this.props.handleSubmit}/>
)
}
}
I am fetching data in componentDidMount() (I am getting them in the form I want) and I want to save them in the component state with this.setState.
The state is not changing.
I console log that I am getting to the point where setState is called - there are conditions
I tried const that = this
The component is not re-rendering and state is not changing and I would like to know why.
My code:
export class Offers extends Component {
constructor(props) {
super(props);
this.renderOffer = this.renderOffer.bind(this);
this.state = {
...
};
}
componentWillMount() {
this.setState(() => ({
offer: {},
isLoading: true,
isMyOffer: false,
...
}));
}
componentDidMount() {
console.log('MOUNTED');
const { profile } = this.props;
if (profile) {
this.setState(() => ({
isLoading: false
}));
}
if (profile && profile._id) {
this.setState(() => ({
isMyOffer: true,
...
}));
fetch(`/api/offers-by/${profile._id}`,{
method: 'GET'
})
.then(response => response.json())
.then(offers => {
if(!offers || !offers.length) {
this.setState(() => ({
isLoading: false
})
);
} else {
console.log('ELSE', offers[0]._id); // getting proper data
console.log('THIS', this) // getting this object
const offerData = offers[0]
this.setState(() => ({
offer: offerData,
isLoading: false
})) // then
}}) // fetch
console.log('STATE', this.state)
}
console.log('STATE', this.state)
}
setState has a callback method as the second argument.You should use that after the initial setState.This works because setState itself is an asynchronous operation.The setState() method does not immediately update the state of the component but rather if there are multiple setStates, they will be batched together into one setState call.
this.setState(() => ({
isLoading: false
}),() =>{
/// You can call setState again here and again use callback and call fetch and invoke setState again..
});
Ideally you could refactor some of your setStates into a single setState call.Start with an empty object and add properties to your object based on conditons.
const updatedState ={}
if(loading){
updatedState.loading = false
}
if(profile &&..){
updatedState.someProperty = value.
}
this.setState(updatedObject,()=> {//code for fetch..
}) // Using the object form since you don't seem to be in need of previous State.
Why am I getting this error?
Warning: Can't call setState (or forceUpdate) on an unmounted
component. This is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscriptions and asynchronous tasks
in the componentWillUnmount method.
postAction.js
export const getPosts = () => db.ref('posts').once('value');
components:
constructor(props) {
super(props);
this.state = { posts: null };
}
componentDidMount() {
getPosts()
.then(snapshot => {
const result = snapshot.val();
this.setState(() => ({ posts: result }));
})
.catch(error => {
console.error(error);
});
}
componentWillUnmount() {
this.setState({ posts: null });
}
render() {
return (
<div>
<PostList posts={this.state.posts} />
</div>
);
}
As others mentioned, the setState in componentWillUnmount is unnecessary, but it should not be causing the error you're seeing. Instead, the likely culprit for that is this code:
componentDidMount() {
getPosts()
.then(snapshot => {
const result = snapshot.val();
this.setState(() => ({ posts: result }));
})
.catch(error => {
console.error(error);
});
}
since getPosts() is asynchronous, it's possible that before it can resolve, the component has unmounted. You're not checking for this, and so the .then can end up running after the component has unmounted.
To handle that, you can set a flag in willUnmount, and check for that flag in the .then:
componentDidMount() {
getPosts()
.then(snapshot => {
if (this.isUnmounted) {
return;
}
const result = snapshot.val();
this.setState(() => ({ posts: result }));
})
.catch(error => {
console.error(error);
});
}
componentWillUnmount() {
this.isUnmounted = true;
}
React component's state is a local entity. Unmounted component dont have state, no need to do that. React is already telling you this is a no-op which means no-operation in tech speak. It means that you telling component to do something when its already destroyed.
https://reactjs.org/docs/react-component.html#componentwillunmount
You should not call setState() in componentWillUnmount() because the component will never be re-rendered. Once a component instance is unmounted, it will never be mounted again.
Remove this
componentWillUnmount() {
this.setState({ posts: null });
}
it's useless
From the doc:
You should not call setState() in componentWillUnmount() because the
component will never be re-rendered. Once a component instance is
unmounted, it will never be mounted again.
https://reactjs.org/docs/react-component.html#componentwillunmount
You can try this code:
constructor(props) {
super(props);
this.state = { posts: null };
}
_isMounted = false;
componentDidMount() {
this._isMounted = true;
getPosts()
.then(snapshot => {
const result = snapshot.val();
if(this._isMounted) {
this.setState(() => ({ posts: result }))
}
})
.catch(error => {
console.error(error);
});
}
componentWillUnmount() {
this._isMounted = false;
this.setState({ posts: null });
}
render() {
return (
<div>
<PostList posts={this.state.posts} />
</div>
);
}
By using _isMounted, setState is called only if the component is mounted. the answer simply does a check to see if the component is mounted before setting the state.
So far I am calling an api in componentDidMount() and set it to select option.
also call another conditional api from user input.
But Problem is it is calling the api non stop.
**getRates(){
const base = this.handlePrint(this.state.value);
fetch(`https://exchangeratesapi.io/api/latest?base=${base}`)
.then(data => data.json())
.then(data =>{
this.setState({
rate: data.rates,
})
console.log(data.rates)
})
.catch(err => console.log(err))
}**
And my console screen shot:
console
I just need one time api call based on user input.
full code: https://codeshare.io/5MwXzq
I think there is a problem with the state but I am new in reactjs and could not understand how to solve that.
Anyone can help please.
This is happening not because of anything in componentDidMount()
Based on the code you shared on codeshare.io, you're calling getRates() function in your render() method. Also, you're setting the state using setState within the getRates method. This causes a re-render, calling render() again, and so you get the infinite loop of calls.
Remove the call to getRates() from your render method and it'll work.
EDIT:
Since there were small changes but strewn across your code to get it to work, I've modified your class and posting it here:
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
currencies: [],
value: "?",
base: "?",
input: "?",
rate: 0
};
this.getRates = this.getRates.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handlePrint = this.handlePrint.bind(this);
}
componentDidMount() {
fetch("https://exchangeratesapi.io/api/latest?symbols=USD,GBP,AUD,JPY")
.then(data => data.json())
.then(data => {
const currencies = [];
currencies.push(
data.base,
...Object.entries(data.rates).map(rates => rates[0])
);
this.setState({
currencies
});
})
.catch(err => console.log(err));
}
getRates() {
const base = this.handlePrint();
console.log(this.state); fetch(`https://exchangeratesapi.io/api/latest?base=${base}`)
.then(data => data.json())
.then(data => {
this.setState({
rate: data.rates
});
console.log(data.rates);
})
.catch(err => console.log(err));
}
//Dropdown
DropDown = function(list) {
return <option value={list}>{list}</option>;
};
handleChange(e) {
this.setState({ value: e.target.value });
}
handlePrint() {
console.log(this.state)
if (this.state.value) {
return this.state.value;
}
};
render() {
const { currencies } = this.state;
// const input = this.getRates();
return (
<div>
<span>SELECT your Base: </span>
<select autoFocus onChange={this.handleChange}>
<option inputcurrency={currencies} selected data-default>
SELECT BASE
</option>
{currencies.map(this.DropDown)}
</select>
<button onClick={this.getRates}>GET RAtes</button>
<p>selected base:{this.handlePrint()} </p>
</div>
);
}
}
The changes are:
1. Bound getRates() method in the constructor
2. Removed the call to getRates() in render start
3. Removed unnecessary items passed to handlePrint
4. Changed the button onClick to point to getRates
I'm making a Ajax request to a Json file that return some movies.
state = { movies: [] };
componentWillMount()
{
this.getMovies();
}
/*
Make an ajax call and put the results in the movies array
*/
getMovies()
{
axios.get('https://pastebin.com/raw/FF6Vec6B')
.then(response => this.setState({ movies: response.data }));
}
/*
Render every movie as a button
*/
renderMovies()
{
const { navigate } = this.props.navigation;
return this.state.movies.map(movie =>
<ListItem key={ movie.title }
title={ movie.title }
icon={{ name: 'home' }}
onPress={() =>
navigate('Details', { title: movie.title, release: movie.releaseYear })
}
/>
);
}
render() {
return(
<List>
{ this.renderMovies() }
</List>
);
}
The error I get is the following: this.state.map is not a function. This is because movies is still empty.
When I console.log response.data it returns all the rows from the JSON file. So the problem is most likely in this line:
.then(response => this.setState({ movies: response.data }));
Does someone know what's wrong?
You put initial state in the wrong place. Do this instead:
constructor(props) {
super(props);
this.state = { movies: [] };
}
From document:
In general, you should initialize state in the constructor, and then
call setState when you want to change it.
Update you ajax request as following:
/*
Make an ajax call and put the results in the movies array
*/
getMovies()
{
let self = this;
axios.get('https://pastebin.com/raw/FF6Vec6B')
.then(response => self.setState({ movies: response.data }));
}
Also, you can bind your function inside constructor as:
constructor(props){
super(props);
this.getMovies = this.getMovies.bind(this);
}