Updating state of a class inside a function in React - javascript

I am trying to update the state of this class with the array of objects stored in the variable childData. However, when I use setState({childData: childData)}, and use it later on by calling this.state.childData, it is undefined, so it never updates the states with the information.
class Users extends React.Component {
state = {
childData: ""
}
retrieve = () => {
let childData;
var leadsRef = database.ref('users');
leadsRef.on('value', function(snapshot) {
childData = snapshot.val();
console.log(childData)
this.setState({
childData: childData
})
});
}
componentDidMount() {
this.retrieve()
}
render() {
return(
<div>
<h3>These are all the users in the app</h3>
{console.log(this.state.childData)}
</div>
)
}
}
export default Users

You have a couple issues going on. First, you do indeed need to set state within the callback function. However, as is, you'll hit an infinite loop. That's because you shouldn't be performing the async function in the render method. Instead, do it in the componentDidMount method so it only fires when the component mounts.
class Users extends React.Component {
state = {
childData: ""
}
retrieve = () => {
let childData;
var leadsRef = database.ref('users');
leadsRef.on('value', snapshot => {
childData = snapshot.val();
console.log(childData)
this.setState({
childData: childData
})
});
}
componentDidMount() {
this.retrieve()
}
render() {
return(
<div>
<h3>These are all the users in the app</h3>
{console.log(this.state.childData)}
</div>
)
}
}
export default Users

Try setting state inside the leadsRef.on callback function. For example:
leadsRef.on('value', snapshot => {
const childData = snapshot.val()
this.setState({childData})
})

Use this.setState in your callback. The code you are executing is non blocking so this.setState will be executed before you retrieved childDate.
Also make you callback function an arrow function.
Is this helpfull, I am not sure if it is correct.

Related

calling externally componentDidMount() in react

I have a requirement in which once page gets loaded my dropdownlist should be populated. for that I put that code in componentDidMount().
componentDidMount() {
axios.get(`http://localhost:8080/country_code`).then((res) => {
const countryData = res.data;
this.setState({ countryData });
alert(countryData);
});
}
I have one user input field in which person enter the value and save it into database. I want once user save that value into DB, my dropdown should get refresh and that value should be visible in the dropdown. How can I externally call componentDidMount()? is there any better way to handle the same?
As of now list is getting refreshed only when user resfresh the page.
You can't call externally componentDidMount() method !. so you need set
common function which is call in componentDidMount() and onChange dropdown value. see below code !
class App extends Component {
componentDidMount() {
this.handleCallApi();
}
handleCallApi = () => {
axios.get(`http://localhost:8080/country_code`).then((res) => {
const countryData = res.data;
this.setState({ countryData });
alert(countryData);
});
}
render() {
return (
<div>
<button onClick={this.handleCallApi}>Call Api</button>
</div>
);
}
}
export default App;
You can't call componentDidMount externally but you can extract the code in componentDidMount to a method and can call it in both componentDidMount and onSave.
alertDropDown = () => {
axios.get(`http://localhost:8080/country_code`).then((res) => {
const countryData = res.data;
this.setState({ countryData });
alert(countryData);
});
}
componentDidMount
componentDidMount() {
this.alertDropDown()
}
On DB save method
onSave = () => {
this.alertDropDown()
}
You can't call the componentDidMount(), as it's a lifecycle method and is called at initial render. You can expose a function and call that function from inside the componentDidMount() something like:
updateDropdownData = () => {
axios.get(`http://localhost:8080/country_code`).then((res) => {
const countryData = res.data;
this.setState({ countryData });
alert(countryData);
});
}
componentDidMount() {
this.updateDropdownData()
}
And you can call this.updateDropdownData() from anywhere you want. Just like:
onSomeUserAction = () => {
this.updateDropdownData()
}

Issue iterating state from firebase in react

I have this component in react that get todo from firebase, build an array and set a state, but when I render the component I only can see the elements the first time, if I reload the page the state seems to be empty.
import React, { PureComponent } from 'react';
import firebase from 'firebase'
class Charts extends PureComponent {
constructor(props) {
super(props);
this.state = { data: [] };
this.todoRef = firebase.database().ref('Todo');
}
componentDidMount = () => {
var data = [];
this.todoRef.on('value', (snapshot) => {
const todos = snapshot.val();
for (let id in todos) {
data.push({ id, ...todos[id] });
}
});
this.setState({ data: data })
}
render() {
return <div>
{this.state.data.map(item => (
<p>{item.name}</p>
))}
</div>
}
}
export default Charts;
If I use console log I get an array(0) with elements inside. I have tried locating the setState in different life cicles methods but don't seems to work.
Issue
You are calling this.setState outside the snapshot handler, so you are only passing the empty ([]) data array to the state updater.
Solution
You should move the state update into the function processing the snapshot.
componentDidMount = () => {
const data = [];
this.todoRef.on('value', (snapshot) => {
const todos = snapshot.val();
for (let id in todos) {
data.push({ id, ...todos[id] });
}
this.setState({ data });
});
}

Why is this function call in render() creating an infinite loop?

I want to call a function in render() which will update the state. But when I do that, it gives me this error message:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I don't understand why this is happening because I'm not directly setting the state in my render(), I'm setting it in my setInsightUrl() function.
I've tried using different lifecycle functions but couldn't get any to work. I'm not sure how else to write this function.
import React, { Component } from 'react'
import "../../css/tabs/Tabs.css"
import {connect} from "react-redux"
class InsightPage extends Component {
constructor() {
super();
this.state = {
insightUrlState: null
}
this.setInsightUrl = this.setInsightUrl.bind(this);
}
setInsightUrl(url) {
this.setState({
insightUrlState: url
})
console.log(this.state.insightUrlState, 'INSIGHTTTTTT URLLLLLbgnhjm,k.l')
}
render() {
this.props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === this.props.insightNameReducer) {
{this.setInsightUrl(insight.insightURL)}
}
})
})
})
return (
<div className={this.props.drawerOpen ? "tab_container2" : "tab_container" }>
<h1>Hello from Insight</h1>
<iframe frameBorder="0" style={{width: "100%", height: "70vh"}} src="https://insighttbdashboards.verizon.com/t/DigtalAnalytics/views/Digital_Analytics/Chat_Dashboard?iframeSizedToWindow=true&:embed=y&:showAppBanner=false&:display_count=no&:showVizHome=no#2" />
</div>
)
}
}
const mapStateToProps = state => ({
drawerOpen: state.SideDrawerReducer.open,
sideTreeMenu: state.SideDrawerReducer.menu,
insightNameReducer: state.SideDrawerReducer.insightName
})
export default connect(mapStateToProps)(InsightPage);
It should update the state with the url I am passing into the function in the render block.
Just because you are calling setState in a function defined outside of render (setInsightUrl) doesn't mean you aren't calling it within render, render potentially calls setInsightUrl when the right conditions are met, and thus can potentially loop forever.
Perhaps you could update the state only if it actually is changing:
setInsightUrl(url) {
if (this.state.insightUrlState != url) {
this.setState({
insightUrlState: url
})
console.log(this.state.insightUrlState, 'INSIGHTTTTTT URLLLLLbgnhjm,k.l')
}
}
From the code you posted (I'm not sure if that is the full code for your component) there's no need to determine the insight url in the render() function. If you do want to determine it in the render function (which should be the last thing your component does) then you shouldn't need to put it in the state, you should just use a local variable for it.
But if you want it in the state, you can either do it in the constructor:
constructor(props) {
super(props);
let insightUrlState = null;
props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === props.insightNameReducer) {
insightUrlState = insight.insightURL;
}
});
});
});
this.state = { insightUrlState };
}
With an additional use of a lifecycle method if you want to update the state when the props change:
componentDidUpdate(prevProps, prevState) {
// depending on how many items are in these arrays, you might want to
// wrap this in a check to see if this.props.sideTreeMenu.dynamicMenu has
// changed from prevProps.sideTreeMenu.dynamicMenu and/or if
// this.props.insightNameReducer has changed from prevProps.insightNameReducer
let insightUrlState = null;
this.props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === this.props.insightNameReducer) {
insightUrlState = insight.insightURL;
}
});
});
});
if (prevState.insightUrlState !== insightUrlState) {
this.setState({ insightUrlState });
}
}
Or, alternatively, you can use the getDerivedStateFromProps function to determine the insightUrlState value just before rendering (using this function, you don't need to use the constructor or componentDidUpdate options):
static getDerivedStateFromProps(props) {
let insightUrlState = null;
props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === props.insightNameReducer) {
insightUrlState = insight.insightURL;
}
});
});
});
return { insightUrlState };
}
this.props.sideTreeMenu.dynamicMenu.forEach(obj => {
obj.children.forEach(child => {
child.insights.forEach(insight => {
if (insight.insightName === this.props.insightNameReducer) {
{this.setInsightUrl(insight.insightURL)}
}
})
})
})
This block is not valid JSX, you might need to move that to componentDidMount.
You can't call setState inside render, otherwise will cause a re-render, so it will go again to render and so on... That's why you got that error.

setState error on unmounted component when with data from Firebase

When the component below is mounted, everything Firebase related works fine. The issue occurs when the data in Firebase is updated. I then navigate to a different route, therefore un-mounting this component and the setState error occurs.
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component
I have tried turning the Firebase functions 'off' in componentWillUnmount by i still seem to be hit with the error. Any help would be appreciated
constructor() {
super();
this.state = {
firebaseData: {}
};
}
componentDidMount() {
const { referenceId } = this.props.episode || '';
if (referenceId) {
this.getFirebaseData(this.removeDissallowedChars(referenceId));
}
}
componentWillReceiveProps(nextProps) {
if (this.props.values.referenceId !== nextProps.values.referenceId) {
this.setState({
referenceId: nextProps.values.referenceId,
}, this.fetchWorkflows);
}
}
getFirebaseData(refId) {
const database = firebase.database().ref(`workflows/sky/${refId}`);
database.on('value', snapshot => {
this.setState({ firebaseData: snapshot.val() });
}, error =>
console.log(error)
);
}
componentWillUnmount(refId) {
const database = firebase.database().ref(`workflows/sky/${refId}`);
database.off();
}
removeDissallowedChars(badRefId) {
/**
* BE strip characters that Firebase doesn't allow.
* We need to do the same. Reference id will only contain the characters listed below.
* Change needed in FE as some of our reference id's currently contain period characters.
**/
return badRefId.replace(/[^A-Za-z0-9-:/]+/g, '-');
}
fetchWorkflows() {
const { referenceId } = this.state;
this.props.fetchWorkflows(referenceId);
}
You can have a class variable that keeps track of whether or not your component is mounted. That would look like this:
constructor() {
//...
this._mounted = false;
}
componentDidMount() {
this._mounted = true;
//...
}
componentWillUnmount() {
//...
this._mounted = false;
}
Then on any place you set the state after an async request, you can put an if statement that checks whether or not _mounted is true.
In your case:
getFirebaseData(refId) {
const database = firebase.database().ref(`workflows/sky/${refId}`);
database.on('value', snapshot => {
// Check if component is still mounted.
if (this._mounted) {
this.setState({ firebaseData: snapshot.val() });
}
}, error =>
console.log(error)
);
}

API taking too long, map function firing before data loads

import React, { Component } from 'react';
import {withProvider} from './TProvider'
import ThreeCardMap from './ThreeCardMap';
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
componentDidMount(){
this.props.getList()
this.setState({newlist: [this.props.list]})
}
// componentDidUpdate() {
// console.log(this.state.newlist);
// }
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);
Hi, I'm trying to create a page that takes data from a tarot card API (https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major). Unfortunately by the time the data comes in, my map function has already fired. I'm asking to see if there is a way to have the map function wait until the data hits before it fires. Thanks!
Edit: getList function in the Context:
getList = () => {
console.log('fired')
axios.get('https://vschool-cors.herokuapp.com?url=https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major').then(response =>{
this.setState({
list: response.data
})
}).catch(error => {
console.log(error);
})
}
this.props.getList() is an async function. You are setting the list right after that call which is not correct.
You need to set it in the getList promise then() block.
getList() is an async function and update data for the parent component. So, my solution is just watching the list from the parent component if they updated or not, through getDerivedStateFromProps
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
// Set props.list to this.state.newList and watch the change to update
static getDerivedStateFromProps(nextProps, prevState) {
return {
newlist: nextProps.list
}
}
componentDidMount(){
this.props.getList()
// Removed this.setState() from here.
}
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);

Categories

Resources