ComponentDidUpdate() causing memory leaks and drainage of battery in IOS - react-native

I am using componentDidUpdate() method for displaying updated data on screen whenever any functionalities is performed like scanning Qr code and storing data. But after searching on net I found that calling this.setState in componentDidUpdate() creates infinite loop. I tried to remove this problem through including prevState in the above method but I failed. Below is my code for more details. How can I update state coming from Asyncstorage without the help of componentDidUpdate()?
componentDidUpdate() {
try {
AsyncStorage.getItem('Data').then((value) => {
this.setState({
list: JSON.parse(value)
})
})
} catch (err) {
}
}
Any help or suggestions would be helpful Thanks.

Updating the state in componentDidUpdate will trigger another componentDidUpdate call, so you should restrict calling setState(). For example:
componentDidUpdate(prevProps, prevState) {
// Only update state if list is empty
if (prevState.list.length === 0) {
try {
AsyncStorage.getItem('Data').then((value) => {
this.setState({
list: JSON.parse(value)
})
})
} catch (err) {
}
}
}

Related

useState does not work in SQLlite SELECT function react-native [duplicate]

So I have this:
let total = newDealersDeckTotal.reduce(function(a, b) {
return a + b;
},
0);
console.log(total, 'tittal'); //outputs correct total
setTimeout(() => {
this.setState({ dealersOverallTotal: total });
}, 10);
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1'); //outputs incorrect total
newDealersDeckTotal is just an array of numbers [1, 5, 9] e.g.
however this.state.dealersOverallTotal does not give the correct total but total does? I even put in a timeout delay to see if this solved the problem.
any obvious or should I post more code?
setState() is usually asynchronous, which means that at the time you console.log the state, it's not updated yet. Try putting the log in the callback of the setState() method. It is executed after the state change is complete:
this.setState({ dealersOverallTotal: total }, () => {
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
In case of hooks, you should use useEffect hook.
const [fruit, setFruit] = useState('');
setFruit('Apple');
useEffect(() => {
console.log('Fruit', fruit);
}, [fruit])
setState is asynchronous. You can use callback method to get updated state.
changeHandler(event) {
this.setState({ yourName: event.target.value }, () =>
console.log(this.state.yourName));
}
Using async/await
async changeHandler(event) {
await this.setState({ yourName: event.target.value });
console.log(this.state.yourName);
}
The setState is asynchronous in react, so to see the updated state in console use the callback as shown below (Callback function will execute after the setState update)
this.setState({ email: 'test#example.com' }, () => {
console.log(this.state.email)
)}
I had an issue when setting react state multiple times (it always used default state). Following this react/github issue worked for me
const [state, setState] = useState({
foo: "abc",
bar: 123
});
// Do this!
setState(prevState => {
return {
...prevState,
foo: "def"
};
});
setState(prevState => {
return {
...prevState,
bar: 456
};
});
The setState() operation is asynchronous and hence your console.log() will be executed before the setState() mutates the values and hence you see the result.
To solve it, log the value in the callback function of setState(), like:
setTimeout(() => {
this.setState({dealersOverallTotal: total},
function(){
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
}, 10)
If you work with funcions you need to use UseEffect to deal with setState's asynchrony (you can't use the callback as you did when working with classes). An example:
import { useState, useEffect } from "react";
export default function App() {
const [animal, setAnimal] = useState(null);
function changeAnimal(newAnimal) {
setAnimal(newAnimal);
// here 'animal' is not what you would expect
console.log("1", animal);
}
useEffect(() => {
if (animal) {
console.log("2", animal);
}
}, [animal]);
return (
<div className="App">
<button onClick={() => changeAnimal("dog")} />
</div>
);
}
First console.log returns null, and the second one returns 'dog'
just add componentDidUpdate(){} method in your code, and it will work.
you can check the life cycle of react native here:
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
As well as noting the asynchronous nature of setState, be aware that you may have competing event handlers, one doing the state change you want and the other immediately undoing it again. For example onClick on a component whose parent also handles the onClick. Check by adding trace. Prevent this by using e.stopPropagation.
I had the same situation with some convoluted code, and nothing from the existing suggestions worked for me.
My problem was that setState was happening from callback func, issued by one of the components. And my suspicious is that the call was occurring synchronously, which prevented setState from setting state at all.
Simply put I have something like this:
render() {
<Control
ref={_ => this.control = _}
onChange={this.handleChange}
onUpdated={this.handleUpdate} />
}
handleChange() {
this.control.doUpdate();
}
handleUpdate() {
this.setState({...});
}
The way I had to "fix" it was to put doUpdate() into setTimeout like this:
handleChange() {
setTimeout(() => { this.control.doUpdate(); }, 10);
}
Not ideal, but otherwise it would be a significant refactoring.

Simplest way to merge two useState values

I have a react native hook where trips must be updated every time a createdTrips is added to the state:
const [trips, setTrips] = useState([]);
function fetchCreatedTrips() {
try {
API.graphql(graphqlOperation(onCreateTrip)).subscribe({
next: (result) => {
console.log(result);
const updatedTrips = [...trips, result.value.data.onCreateTrip]
setTrips(updatedTrips)
}
})
} catch (err) { console.log(err) }
}
Now, when i first open the screen, it renders all the trips items of the list.
However with the current code, after i create a trip and go back to that screen, it doesn't currently return all the trips + the newly created one, but only one, that is the newly created one. How can i return all the items of the list? Sorry in advance, i'm a beginner.
While setting the new state on your effect you should include the previous values of the trips. You can use the spread operator to do the same.
const createdTrips = [result.value.data.onCreateTrip];
setTrips([...createdTrips, ...trips]);
or better merge the newly created trip in the updateTrips variable and then set it as the state value,
const udpatedTrips = [...trips,result.value.data.onCreateTrip];
setTrips(udpatedTrips);
The problem in your code is you are firing those two methods at the same time and there can be race conditions, so you see random updates on the screen.
Ideally, you need to bring in some consistency in the API calls and the state update. So first fetchTrips()->then->fetchCreatedTrips(). Try the below code wherein I don't update the state immediately in fetchTrips() but rather pass on the results to fetchCreatedTrips() which completes the API call and updates the state together.
const [trips, setTrips] = useState([]);
useEffect(() => {
fetchTrips();
}, [])
async function fetchTrips() {
try {
const tripData = await API.graphql(graphqlOperation(listTrips));
const trips = tripData.data.listTrips.items
fetchCreatedTrips(trips);
} catch (err) { console.log(err) }
}
function fetchCreatedTrips(fetchedTrips) {
try {
API.graphql(graphqlOperation(onCreateTrip)).subscribe({
next: (result) => {
console.log(result);
const updatedTrips = [...fetchedTrips,...updatedTrips];
setTrips(updatedTrips)
}
})
} catch (err) { console.log(err) }
}
PS: Please handle exceptions correctly.

NetInfo.addEventListener is called twice in component did mount in IOS

When app lunches, componentDidMount is called and then NetInfo.addEventListener is called twice.
Is any solution of it. My code is:
class OfflineMessage extends PureComponent {
state = {
isConnected: true
};
componentDidMount() {
NetInfo.addEventListener((state) => {
this.handleConnection(state.isConnected);
});
}
componentWillUnmount() {
NetInfo.removeEventListener((state) => {
this.handleConnection(state.isConnected);
});
}
handleConnection = (isConnected) => {
console.log('status-----', isConnected);
this.setState({ isConnected });
};
This is expected behaviour according to github issue page. You should not make any assumptions about when or how often your listen is called and you should expect differences between platforms. This is because each platform handles networking different and we mirror that rather than trying to make everything 100% the same.
This is likely because a value other than isConnected is changing, for example the network type. We fire the event whenever the system provides some new network information and pass this on to you.
If you do not want to re-render when the information is the same, this is up to you to implement either using componentShouldUpdate or by managing the state in something like Redux. The library will update you whenever it gets and update and you shouldn't make assumptions about how often or when it will provide these.
Refer
Refer
I corrected my code, Now even componentDidMount called twice, if connectivity status changed then only It will print console.
Previously whenever connectivity status changed it was printing true, true, false ,false.
class OfflineMessage extends PureComponent {
state = {
status: true
};
componentDidMount() {
NetInfo.isConnected.addEventListener('connectionChange', this.handleConnectionChange);
}
componentWillUnmount() {
NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectionChange);
}
handleConnectionChange = (isConnected) => {
const { status } = this.state;
if (isConnected != status) {
console.log("connection changes");
NetInfo.isConnected.removeEventListener('connectionChange');
} else {
NetInfo.isConnected.removeEventListener('connectionChange');
}
}

Call a function after state changes

I'm building a React Native app and when one button is pressed I want to call two functions. The first one will make a get call and set the state loading: true, the second one will show a popup with the result of that get call.
I am calling the second function only if loading === false but it is executed immediately after the first one before the state can change, because loading is false by default. I can resolve this with setTimeout but I was wondering if there was a cleaner way to do this.
onPress() {
this.props.getUsers();
setTimeout(() => {
if (this.props.loading === false) {
this.props.popUpVisible();
}
}, 1000);
}
You can create callback function for that
getUsers = (callback) => {
//do whatever you want
//when it's done
callback();
}
In onPress function
onPress = () => {
this.props.getUsers(() => {
if (this.props.loading === false) {
this.props.popUpVisible();
}
});
}
setState Function can take two param:
setState(updater, callback)
setState({loading:true},() => {
//this fires once state.loading === true
})
Use getDerivedStateFromProps. It always fire when component's props change.
Below is the example.
class EmailInput extends Component {
state = {
email: this.props.defaultEmail,
prevPropsUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevPropsUserID) {
return {
prevPropsUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}

Failed in retrieve data from AsyncStorage

I am a beginner at react-native.
I trying to retrieve data that stored from screen1.js in Screen2.js but I failed.
I have import Asyncstorage from react-native for both .js
This how I store variable from screenone.js :
class screenone extends Component {
state = {
oldpin: '000000',
newpin: '',
secpin: '',
};
onPressButton = () => {
if (this.state.newpin == this.state.secpin) {
this.setState(
{ oldpin: this.state.newpin },
async() => await this.storeData());
}
else {
ToastAndroid.show("Password Unmatched", ToastAndroid.SHORT);
}
}
storeData = async () =>{
const {oldpin} = this.state;
let pin : oldpin;
try{
await AsyncStorage.setItem('mypin',pin);
ToastAndroid.show("Password Changed", ToastAndroid.SHORT);
}
catch (err){
console.warn(err)
}}
....
This is how I trying to retrieve data in screentwo.js:
class screentwo extends Component {
constructor(props) {
super(props);
this.onComplete = this.onComplete.bind(this);
this.state = {
pin: ''
};
}
retrieveData = async (mypin) => {
try {
let value = await AsyncStorage.getItem(mypin);
if (value !== null) {
console.log(value);
this.setState({
pin: value})
}
} catch (error) {
console.warn(err)
}
}
onComplete(inputtedPin, clear) {
retrieveData();
if (inputtedPin !== this.state.pin) {
ToastAndroid.show("Incorrect Pin", ToastAndroid.SHORT);
clear();
} else {
ToastAndroid.show("Pin is correct", ToastAndroid.SHORT);
clear();
this.props.navigation.navigate("Dashboard");
}}
....
Error:
Reference Error: ReferenceError:Can't find variable:retrieveData
Am I using the right way to stored and retrieve data?
Any suggestion?
Thank you.
There are a couple of issues that I can see with your code.
Firstly the retrieveData() function. It is asynchronous and should be called with await also you are getting the error: Reference Error: ReferenceError:Can't find variable:retrieveData because you haven't used this
So ideally you should call it await this.retrieveData();
There are a few more issues with this function. You use the parameter mypin but don't seem to pass any parameter to the function when you call it. Fixing this issue you should call retreiveData() like this:
await this.retrieveData('mypin');
Or you could remove passing the paramater altogether, which I will show how to do in my refactor below.
Finally you call retreiveData every time you check the inputtedPin this isn't that efficient, it is asynchronous so it may take some time, and secondly it also takes time for the setState function to complete, which means that the state may not have updated in time when you go to check it against the inputtedPin, meaning that you are checking the inputtedPin against the wrong value.
Code Refactor
This is how I would refactor your component.
Refactor retrieveData so that it no longer takes a parameter and the key is hardcoded in the .getItem
In the componentDidMount get the value of the pin from AsyncStorage and save it to state.
Remove the retrieveData call from onComplete
Here is the refactor
retrieveData = async () => { // parameter have been removed
try {
let value = await AsyncStorage.getItem('mypin'); // notice I am now passing the key as a string not as a parameter
if (value !== null) {
console.log(value);
this.setState({ pin: value })
}
} catch (error) {
console.warn(err)
}
}
// here we call the refactored retrievedData which will set the state.
async componentDidMount () {
await this.retrieveData();
}
onComplete(inputtedPin, clear) {
// we remove the call to retrieveData as we have already gotten the pin in the componentDidMount
if (inputtedPin !== this.state.pin) {
ToastAndroid.show("Incorrect Pin", ToastAndroid.SHORT);
clear();
} else {
ToastAndroid.show("Pin is correct", ToastAndroid.SHORT);
clear();
this.props.navigation.navigate("Dashboard");
}
}
only replace
retrieveData();
to
this.retrieveData();
When you call async method from a caller method that method also become async Try prefix
async onComplete () { await this.retrieveData() }