Changing state in ComponentWillMount goes to an infinite loop - react-native

In my react native app i'm changing a style at a fixed time from 7 to 7:30pm. I am changing the state for that in ComponentWillMount. But whenever i go to that component,it starts calling that state again and again and doesn't even stop when i go to a different component. I want to stop this infinite loop of calling itself.
Here's the code:
import { withNavigation } from "react-navigation";
class Third extends Component {
constructor(props) {
super(props);
this.state = {
toggle: 0,
live: false
}
}
componentWillMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener("didFocus", () => {
this.changeLang()
});
var today = new Date()
var time = today.getHours()
console.log(today.getMinutes())
var weekDay = today.getDay()
if ((time >= 19) && (time <= 20 ) && (weekDay === 0 ||3 ||6)){
if(today.getMinutes()<=30){
this.setState({ live: true })
}
}
}
async changeLang() {
try {
const value = await AsyncStorage.getItem('toggle')
this.setState({ toggle: JSON.parse(value) })
} catch (e) {
// error reading value
}
}
render() {
const state = this.state;
console.log('live', this.state.live)
this.changeLang()
return (
<Container style={{ backgroundColor: '#fff' }}>
<Content>
<Left></Left>
<Body></Body>
<Right>{(this.state.live === true) ? <Icon name='football'/>: <Icon name='refresh'/>}</Right>
</View>
</Card>
</View>
</Content>
</Container>
);
}
}
export default withNavigation(Third)
Here this.state.live keeps on giving consoles and doesn't stop. What can be done here to resolve this?

Your problem is with this in render function,
this.changeLang()
Every time you call this.setState your component will re-render, and when component re-render your render function will get called.
So the sequence causing infinite loop is,
Component mounts => in render function you call this.changeLang() => in changeLog function you are calling this.setState => render function get called which again executes this.changeLang() => in changeLog function you are calling this.setState ... so on.
In this way you end up with infinite loop.
Just remove this.changeLang() from render function as you are already calling the same in componentWillMount.

Calling setState here makes your component a contender for producing infinite loops. remove this.changeLang() make it work

Related

React Native component not re-rendering when state is updated

I have a component in my React Native app that displays a list of pending friends. This component makes a GET request to an API to retrieve the list of pending friends and then uses a useEffect hook to map over the list and render each friend as a Pressable component. I'm also using the useFocusEffect hook to make the get request when the screen renders.
Here is the relevant code for the component:
const Pending = () => {
const [pendingFriends, setPendingFriends] = useState(null)
let pendingFriendsRender = []
useEffect(() => {
if (pendingFriends !== null) {
for(let i = 0; i < pendingFriends.length; i++) {
pendingFriendsRender.push(
<Pressable key={i} style={styles.friend}>
<Text style={styles.friendText}>{pendingFriends[i].username}</Text>
</Pressable>
)
}
}
}, [pendingFriends])
useFocusEffect(
useCallback(() => {
async function fetchData() {
const accessToken = await AsyncStorage.getItem('accessToken')
try {
const res = await instance.get('/pending_friends', {
headers: { authorization: `Bearer ${accessToken}`},
})
setPendingFriends(res.data)
} catch (error) {
console.log(error.response.status)
}
}
fetchData()
}, [])
)
return(
<View style={styles.friendsContainer}>
{pendingFriendsRender}
</View>
)
}
I have tried using an empty array as the second argument in the useEffect hook but that approach has not worked. I also tried removing the useEffect hook so the if statement with the for loop stands at the top of the component without the hook, that worked but I can't update it in this way after the component rendered. I checked the API and it is returning the correct data.
The first useEffect you have really isn't needed. You can map through your state inside of your JSX. Anytime the state changes, the component will be re-rendered:
// Need a default here, could also set some loading state when fetching your data
if(pendingFriends === null) {
return <>Loading...</>
}
return(
<View style={styles.friendsContainer}>
{pendingFriends.map((friend, i) => {
return (
<Pressable key={friend.id} style={styles.friend}>
<Text style={styles.friendText}>{friend.username}</Text>
</Pressable>
)
})}
</View>
)
Also keep in mind, it's not recommended to use the index as the key, it can lead to unexpected bugs and issues. Instead use a unique string key (as shown above).
React: using index as key for items in the list
pendingFriendsRender should be the state:
const [pendingFriendsRender, setPendingFriendsRender] = useState([])
Instead of
let pendingFriendsRender = []
Then just clone the array so you lose reference to the object and add the new element
const newPendingFriendsRender = [...pendingFriendsRender, newElement]
or you can use FlatList to make it easier.

How can i refresh data with setInterval when Actions.pop()?

I'm trying to create live dashboard mobile app with react-native. I setInterval to fetch data every 5 sec. When i go to other actions i clearIntervar(cause if i don't clear it continues other pages) and it's ok but when i try to Action.pop() i cant setInterval again.
I tried to setInterval in componentWillUnmount() and Action.refresh(with same props) but every time; i get the same error.
Warning: Can't perform a React state update 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.
This is the sample like my code:
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
isRefresh: false
}
}
componentDidMount() {
this.getData()
}
async getData() {
//just a sample
const data = await fetch(url).then((response) => response.json());
this.setState({data: data});
if (this.state.isRefresh) {
const intervalId = setInterval(() => {
this.getData();
}, 5000);
this.setState({
intervalId: intervalId,
isRefresh: true
})
}
}
render() {
return (
<View>
<Text>{this.state.data}</Text>
<Button onPress={() => {
clearInterval(this.state.intervalId);
Action.otherPage();
}
} title={'Test Button'}/>
</View>
)
}
}
I have to setInterval and fetch data in the other pages too. So i need to clear when i go to other pages and need to setInterval when i come back with Actions.pop()
Don't store intervalId in state, instead you should make use of instance variable for your interval,
constructor(props) {
super(props);
this.state = {
isRefresh: false
}
this.intervalId = null; //instance variable
}
Then assign your interval to instance variable,
this.intervalId = setInterval(() => { this.getData();}, 5000);
Then use componentWillUnmount to clear interval,
componentWillUnmount(){
clearInterval(this.intervalId);
}
Please use this
componentDidMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener('didFocus', () => {
// The screen is focused
// Call any action you want when a user on this screen
});
}
componentWillUnmount() {
// Remove the event listener
this.focusListener.remove();
}

React Native : Conditional render() based on AsyncStorage result

Trying to use a AsyncStorage variable to conditionally render content.
My app uses createBottomTabNavigator from react-navigation. I have a tab called Settings that must conditionally render content based on wether a user is logged in or not (checking AsyncStorage). The following code works on first render but another tab can update AsyncStorage value, returning back to Settings tab it still renders initial content.
Which approach can i use to achieve this, i'm also trying to use shouldComponentUpdate but i'm not sure how it works.
import React, {Component} from 'react';
class Settings extends React.Component{
constructor(props){
super(props);
this.state = {
isLoggedIn:false
};
}
//I want to use this method but not sure how.
shouldComponentUpdate(nextProps, nextState){
// return this.state.isLoggedIn != nextState;
}
componentDidMount(){
console.log("componentWillUpdate..");
this.getLocalStorage();
}
getLocalStorage = async () => {
try {
const value = await AsyncStorage.getItem('username');
if(value !== null) {
this.setState({isLoggedIn:true});
}
} catch(e) {
// error reading value
}
}
render() {
if(this.state.isLoggedIn)
{
return(
<View>
<Text style={styles.title_header}>Logged In</Text>
</View>
);
}
else{
return(
<View>
<Text style={styles.title_header}>Logged Out</Text>
</View>
);
}
}
}
export default Settings;
})
Use NavigationEvents. Add event listeners to your Settings components.
onWillFocus - event listener
onDidFocus - event listener
onWillBlur - event listener
onDidBlur - event listener
for example, the following will get fired when the next screen is focused.
focusSubscription = null;
onWillFocus = payload => {
// get values from storage here
};
componentDidMount = () => {
this.focusSubscription = this.props.navigation.addListener(
'willFocus',
this.onWillFocus
);
};
componentWillUnmount = () => {
this.focusSubscription && this.focusSubscription.remove();
this.focusSubscription = null;
};
The problem comes from react-navigation createBottomTabNavigator. On first visit, the component is mounted and so componentDidMount is called and everything is great.
However, when you switch tab, the component is not unmounted, which means that when you come back to the tab there won't be any new call to componentDidMount.
What you should do is add a listener to the willFocus event to know when the user switches back to the tab.
componentDidMount() {
this.listener = this.props.navigation.addListener('willFocus', () => {
AsyncStorage.getItem('username').then((value) => {
if (value !== null) {
this.setState({ isLoggedIn: true });
}
catch(e) {
// error reading value
}
});
});
}
Don't forget to remove the listener when the component is unmounted:
componentWillUnmount() {
this.listener.remove();
}

Make react-native component blink at regular time interval

I am trying to make a component "blink" on my page. I was thinking about setting a visible: true state in my componentWillMount method and then put a timeout of 1s in componentDidUpdate to set state to the "opposite" of the previous state. As I see it the component lifecycle looks like this :
sets state to visible to true (componentWillMount that runs only once and is not triggering a rerender)
enters componentdidUpdate
waits 1s
hides component (setstate to visible false)
enters componentDidUpdate
waits 1s
shows component (setstate to visible true)
However my component is blinking but the intervals of hide and show are not regular, they change and dont seem to follow the 1s logic
Here's my component code :
class ResumeChronoButton extends Component {
componentWillMount(){
console.log('in componentWillMount')
this.setState({visible: true})
}
componentDidUpdate(){
console.log('in componentDidUpdate')
setTimeout(() =>this.setState({visible: !this.state.visible}), 1000)
}
// componentWillUnmount(){
// clearInterval(this.interval)
// }
render(){
const { textStyle } = styles;
if (this.state.visible){
return (
<TouchableOpacity onPress={this.props.onPress}>
<Pause style={{height: 50, width: 50}}/>
</TouchableOpacity>
);
}
else {
return (
<View style={{height: 50, width: 50}}>
</View>
)
}
}
};
How can I make my component blink at regular time interval.
The following works for me
componentDidMount = () => {
this.interval = setInterval(() => {
this.setState((state, props) => {
return {
visible: !state.visible,
};
});
}, 1000);
};
componentWillUnmount = () => {
clearInterval(this.interval);
};
and then your render can just check this.state.visible to determine if it needs to show or not.
alternatively you could change the setState to
this.setState({visible: !this.state.visible})
Most likely because you are using the state and timeouts. State is set asynchronously and, for this reason, it may take different amounts of time to change the value depending on how many resources you are using.
To achieve the effect you want I would recommendo you to use the Animation framework from React Native. Check the docs.
just use
setInterval(()=>{//setstate here},time_in_ms)

How to update the FlatList immediately after state changed?

I am working with react native.
I have component listing by using
And, when the state to give data to update the list change. It won't update immediately. It take few seconds to re-render.
so, how can I update the component immeidately
//Listcomponent
const ListGlossary = ({glossaries, onPressGlossary, navigation, searchField}) => {
return (
<FlatList
data={glossaries}
keyExtractor={(item) => item.key}
renderItem={({item}) =>
<TouchableHighlight
onPress = {() => navigation.navigate('DetailGlossaryScreen', { searchField: searchField, word: item.word, translate: item.translate})}>
<ListItem
key={`${item.key}`}
title={`${item.word}`}
/>
</TouchableHighlight>
}
/>
}
//And you can find here the home screen component
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
glossaries: [],
searchField: '',
}
}
componentDidMount () {
Promise.resolve().then(() => {this.setState({glossaries: JSONDataFromFile, isLoading: false})})
}
onSearchChange = (inputText) => {
this.setState({searchField: inputText});
}
render(){
return(
let filteredWords = []
if(this.state.searchField != null) {
let searchField = this.state.searchField.toLowerCase(),
glossaries = this.state.glossaries;
for(let i = 0, l = glossaries.length; i < l; ++i) {
if(glossaries[i].word.toLowerCase().indexOf(searchField) === 0){
filteredWords.push(glossaries[i]);
}
}
}
{this.state.isLoading ?
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
:
<ListGlossary
navigation = {this.props.navigation}
glossaries = {filteredWords}
onPressGlossary={this.onPressGlossary}
searchField = {this.state.searchField}
/>
}
)
}
Please show the whole component, and give the length of the list.
--- Edit
I suspect you're doing too much work in the render function. You're filtering every time it gets called, and since you're passing in the navigation prop (I assume you're using React-Navigation), it'll get called frequently. If you're using a stack navigator, all the other screens are also getting re-rendered every time you navigate to a new screen. Avoid passing navigation as much as possible, or use a HOC composition to ignore it.
You probably don't need to be filtering glossaries every time the user changes the search value. Use the shouldComponentUpdate lifecycle method.