react-native componentDidMount to fetch from server - javascript

constructor(props) {
super(props);
console.log("props");
this.state = {
userId : "12345",
};
}
componentDidMount() {
console.log("componentDidMount");
Actions.getProductDetails({userId:"123456"});
Actions.getProductDetails.completed.listen(this.gotProductDetails.bind(this));
Actions.cancelOrder.completed.listen(this.cancelOrderCompleted.bind(this));
}
gotProductDetails(data) {
console.log("gotProductDetails");
}
goBack(data) {
console.log("justgoback");
this.props.back();
}
cancelProduct() {
console.log("SDsadsadsad");
Actions.cancelOrder({
orderId:this.state.order.id,
canelMsg:this.state.selectedReason,
userId:this.state.userId
});
}
cancelOrderCompleted(data) {
console.log("cancelOrderCompleted");
this.goBack();
}
My issue is some functions are mounting twice whenever I change the
route and revisit this route again I would show you console.log here
This is for first time I come to this route:
props
cancelOrder.js:190 componentDidMount
cancelOrder.js:197 gotProductDetails
Now I will do cancelProduct call and log will be
SDsadsadsad
cancelOrder.js:221 cancelOrderCompleted
cancelOrder.js:210 justgoback
This is for second time i.e, I will go back from this route and revisit:
props
cancelOrder.js:190 componentDidMount
cancelOrder.js:197 gotProductDetails
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the cancelOrder component.
cancelOrder.js:197 gotProductDetails
Now I will do cancelProduct call and log will be
SDsadsadsad
cancelOrder.js:221 cancelOrderCompleted
cancelOrder.js:210 justgoback
cancelOrder.js:221 cancelOrderCompleted
cancelOrder.js:210 justgoback
In the above log you can see that for the second time line number 197 221 210 executed twice with the error I was not able to solve
I'm using react navigator for route
I checked in release version also, but it is having same error it was told in one Github issue, but was not able to find now.

Every time you run this line
Actions.cancelOrder.completed.listen(this.cancelOrderCompleted.bind(this));
The listen method gets a new function instance every time it runs, so if this page was mounted twice in the app's lifecycle, the cancelOrderCompleted would run twice and one of them probably in an unmounted component which is bad.
Generally I would advise that your getProductDetails would return a Promise. If you don't want to do that, make sure you remove the listeners when your component is unmounted.
And be aware that cancelOrderCompleted.bind(this) creates a new delegate instance that you can't recreate when stopping the listener. Unless you keep it in a data member.
Edit:
Code example -
constructor(props) {
super(props);
console.log("props");
this.state={
userId : "12345",
}
this.getProductDetailsBound = this.gotProductDetails.bind(this);
this.cancelOrderCompletedBound = this.cancelOrderCompleted.bind(this);
}
componentDidMount() {
console.log("componentDidMount")
// Listen before you call getProductDetails, not after
Actions.getProductDetails.completed.listen(this.getProductDetailsBound);
Actions.cancelOrder.completed.listen(this.cancelOrderCompletedBound);
Actions.getProductDetails({userId:"123456"});
}
componentWillUnmount() {
Actions.getProductDetails.completed.stopListening(this.getProductDetailsBound);
Actions.cancelOrder.completed.stopListening(this.cancelOrderCompletedBound);
}

Related

Best way to run a function when page refresh

I am trying to call a function when the page is refreshed. I adding a state if the page is rendered with the data I got from my backend end but I get an warning message "Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state." Even though it works fine (except with the warning message), I dont think this is the best, most efficient way to do it?
If this is the best, most efficient way, how do I fix the waring message?
function Demo() {
constructor(){
this.state = {
username: "unknown",
rendered: false,
}
this.renderUserProfile = this.renderUserProfile.bind(this);
}
update(){
//code to retrieve data from backend node.js *
this.setState({ username: data });
this.setState({ rendered: true });
}
render(){
if (!this.state.rendered) {
this.update();
}
return (<p>demo</p>)
}
}
Thank you for your help!
Do never change state inside render, because every state (or prop) change will call render again. That is what the warning is telling you: you risk having infinite loops.
There is no need of a state param for "rendered", because your component will call render anyway as soon as this.setState({username: data}); executes. If you want something to happen then, add it in update just after the setState line.
Now let's imagine that you still really want it. If you don't want your component to render when the rendered state changes, then just don't use the React Component state, but any standard class attribute:
class MyComponent extends React.Component {
rendered = false
...
render() {
this.rendered = true
....
}
}
Just be aware that this looks super wrong (and useless) since it tries to go around what the React framework is good at.
Finally, from this code there is no way to know how you intend you have new data coming in. If it is an Ajax call, then you will call this.update with that data in the callback of your Ajax call - certainly not in render.

Memory leak in my React app because of firebase and how to avoid it?

The app I'm working on displays a user dashboard on login with a sidebar for navigation. It uses firebase. I do most of my data fetch from firebase in my async componentDidMount() and store the data in my component state. It takes a couple of seconds to finish all fetches. But if the user decides to navigate to another screen before the fetch is complete, I get the
Can't call setState on unmounted component
warning (as expected). So I do some digging and find that
if(this.isMounted()) this.setState({updates:updates})
makes the warning go away, but then I also find that using isMounted is an antipattern.
The official documentation on this issue suggests tracking the mounted state ourselves by setting _isMounted=true in componentDidMount and then set it to false in the componentWillUnmount. The only way I see to achieve this would be through a variable in component state. Turns out, setState doesn't work in componentWillUnmount. [Issue 1] (I tried calling an external function from componentWillUnmount which in turn sets the state variable. Didn't work.)
The documentation suggests another way, to use cancellable promises. But I'm clueless about how to achieve that with await firebase calls. I also couldn't find any way to stop firebase calls mid-track. [Issue 2]
So now I'm stuck with the warning and data leaks.
a. How do I resolve this problem?
b. Is this something I need to take seriously?
It's good practice to check if the component is still mounted when a request completes, if there is a risk of the component unmounting.
You don't need to put _isMounted in your component state since it will not be used for rendering. You can put it directly on the component instance instead.
Example
class MyComponent extends React.Component {
state = { data: [] };
componentDidMount() {
this._isMounted = true;
fetch("/example")
.then(res => res.json())
.then(res => {
if (this._isMounted) {
this.setState({ data: res.data });
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
// ...
}
}

componentDidMount or componentWillMount which one I need to use

I created a a box similar to twitter using react. I was looking at the react documentation found several component life cycles but not sure which one I should use to improve my code performance: componentDidMount or componentWillMount?
When I type something in my text box I see an update in the console printing the text box value. Can anyone help me understand which method to use and when in this case?
https://jsfiddle.net/c9zv7yf5/2/
class TwitterBox extends React.Component {
constructor(props) {
super(props);
this.state = { enteredTextBoxvalue : '' };
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({enteredTextBoxvalue: event.target.value});
if((event.target.value).length > 3) {
this.setState({className : 'wholeContainer'});
//console.log("long characters");
}
}
render() {
return (<div>Hello {this.props.name}
<textarea className={this.state.className}
value={this.state.enteredTextBoxvalue}
onChange = {this.handleChange}>
there should be only 140 characters
</textarea>
</div>);
}
}
ReactDOM.render(
<TwitterBox name="World" />,
document.getElementById('container')
);
componentWillMount is called right before the component gets rendered.
componentDidMount is called right after the component gets rendered.
If you need to prepare data you use componentWillMount.
componentDidMount is popularly used among sending api calls or grabbing data just right after the component renders and is highly recommended to use that.
componentWillMount:
This function is called right before the component’s first render, so at first glance it appears to be a perfect place to put data fetching logic
componentDidMount:
Using componentDidMount makes it clear that data won’t be loaded until after the initial render. This reminds you to set up initial state properly, so you don’t end up with undefined state that causes errors.
As part of your question is about performance you could consider also having a look at shouldComponentUpdate to avoid reconciliation.
componentWillMount is invoked immediately before mounting occurs. It is called before render().
componentDidMount is invoked immediately after a component is mounted.
componentWillMount
component is about to render, plays the same role as constructor
there is no component in DOM yet you cannot do anything involving DOM
manipulation
calling setState() synchronously will not trigger
re-render as the component is not rendered yet
I would not recommend calling async /api requests here (technically there is no guaranty they will finish before component will be mounted, in this case the your component will not be re-rendered to apply those data)
componentDidMount
component has been rendered, it already seats in the DOM
you can perform manipulations involving DOM elements here (e.g. initialize third-party plugin)
call async /api requests, etc.

Updating view from setState() not triggering rerender when expected

Answer:
I'm a bonehead. This is way late but don't want to leave a thread unanswered and especially since my initial answer was wrong. I needed to reference the new props I was getting, not this.props. As usual the answer was in the documentation. I updated my own answer below to reflect this.
Edit 1:
My first fiddle did not fully show my issue so I've updated it to demonstrate better. For some reason when I call my setState() I believe the first pass through it is undefined even though its given a value, yet on subsequent passes it works as expected. It seems like my initial setState() call is not triggering a rerender but all others are.
A bit different to the usual "setState not updating view" question as setState() IS updating my view and with the correct value. Just not when I expect it to. Basically I am triggering a setState() event should rerender my child component with new props which I believe should trigger the childs components componentWillReceiveProps lifecyle event, which will then call setState() within the child component and update its view. The issue is while it does update the view, it seems to do it a cycle behind when expected. In my MVP I call setState() at 2 seconds yet it updates the view at 4 seconds. I haven't been able to determine which part is the bad logic though.
Here is my jsFiddle MVP. Thanks for any suggestions.
Code:
class TaskBody extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillReceiveProps() {
this.setState({
activeTask: this.props.activeTask
});
}
render() {
return <div>
< h4 > {
this.state.activeTask ? this.state.activeTask : 'Task Title'
} < /h4>
< /div > ;
}
}
class Body extends React.Component {
constructor(props) {
super(props);
this.state = {
activeTask: '',
counter: 1
};
this.updateActive = this.updateActive.bind(this);
}
updateActive(task) {
this.setState({
activeTask: task
});
}
componentDidMount(){
var self = this;
setInterval(function(){
if(self.state.counter === 4){
clearInterval(self.clickInterval);
return clearInterval(self.countInterval);
}
self.setState({ counter: self.state.counter + 1 });
}, 1000);
// imagine this was the click event, it should rerender the view
// instantaneously at 2 seconds because I called setState() right?
// which is supposed to trigger a re-render and then TaskBody should
// receive new props triggering it to setState() on its activeTask,
// which should update the view?
self.clickInterval = setInterval(function(){
self.setState({ activeTask: 'took double the time it should' });
}, 2000);
}
render() {
return <div>
< TaskBody activeTask = {
this.state.activeTask
}/>
<div>{this.state.counter}</div>
</div>;
}
}
ReactDOM.render(<Body />, document.querySelector('#body'));
The
self.setState({ activeTask: 'took double the time it should' });
is never actually called.
Here's why:
Your logic is located within the componentDidMount lifecycle method. If you read the documentation of componentDidMount, you'll see that it clearly states:
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs.
So, when componentDidMount gets called in your app, you're first checking the counter and calling a setState there. That alone will trigger a new render. This means that your second code block within componentDidMount is going to have no effect because while you set the method, it's never going to get called anywhere.
In my old answer, I was using a setTimeout() as a hack to get the results I wanted. Basically, if I wrapped my setState in a timeout it would set the state with the new props, but if I did not it would still reference the old props. Fortunately, this is already handled in react as componentWillReceiveProps already receives a newProps argument by default.
This:
componentWillReceiveProps(){
var self = this;
setTimeout(function(){
self.setState({activeTask: self.props.activeTask});
},0);
}
becomes
componentWillReceiveProps(newProps){
var self = this;
self.setState({activeTask: newProps.activeTask});
}

React mqtt subscription setState warning

I'm using https://www.npmjs.com/package/mqtt with React. In my component I have:
componentDidMount:function(){
client.subscribe('test/topic');
client.on('message',function(topic,message){
if(topic==='test/topic'){
console.log(message.toString());
this.setState({value:parseInt(message.toString())});
}
}.bind(this));
},
componentWillUnmount:function(){
client.unsubscribe('test/topic');
},
So I subscribe to the topic when component will mount and unsubscribe when it unmounts. However, when i go to another view in my app and come back i get a warning with every mqtt message:
Warning: setState(...): Can only update a mounted or mounting component.
This usually means you called setState() on an unmounted component.
This is a no-op.
What am i doing wrong?
I had similar issues at one point. I have since moved back to the older useEffect syntax for my socket listeners and I don't seem to get those warnings now. Not certain that this is the fix but hopefully it helps!
useEffect(() => {
client.on('message' () => {
// update state
}
client.on('disconnect', () => {
// update state
}
})
The callback function is getting called when the didMount method is declared. Move the callback into its own method. You won't need to bind this, either.

Categories

Resources