I need some help to think here. I now get and displays a list of user from my database depending on if the user is set to true or false.
And this works fine, but when i change a user from true to false in the database i would like to update the list that is displayed.
if i re render the page componetWillMount will do the job for me, but i would like to update the list without re render it.
I´ve looked and tried with componentWillUpdate but cant make it work.
class activeUser extends Component {
// Call fetchList to get access to the users
componentWillMount () {
this.props.fetchList()
this.createDataSource(this.props)
}
componentWillReceiveProps (nextProps) {
// nextProps are the next set of props that this component
// will be rendered with
// this.props is still the old set of props
this.createDataSource(nextProps)
}
createDataSource ({ users }) {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
})
this.dataSource = ds.cloneWithRows(users)
}
// Render out the users
renderRow (user) {
return <ListUserItem user={user} />
}
render () {
return (
<View>
<View style={{ height: 320, justifyContent: 'center', alignItems:
'center', marginTop: 35 }} >
<ListView
showsVerticalScrollIndicator={false}
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
</View>
</View>
)
}
}
const mapStateToProps = state => {
const users = _.map(state.list, (val) => {
return {...val}
})
return { users }
}
export default connect(mapStateToProps, {fetchList})(GoingOut)
Related
I'm trying to make a list of buttons based on the input from user input type is an array of options like so multipleOptions = ['1', '2', '3'] then we loop through each option to show a Button can't figure out why it's not working here's my code :
import React, { useState } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
const InputButton = (multipleOptions, likertScale, onPress) => {
const [options, setOptions] = useState([]);
if (likertScale) {
setOptions([...new Array(likertScale).keys()].map((i) => i));
} else if (multipleOptions) setOptions(multipleOptions);
return (
<View style={styles.container}>
{options ? (
options.map((option, i) => (
<View style={[styles.button]} key={`${i}`}>
<TouchableOpacity onPress={() => onPress(option)}>
<Text>{option}</Text>
</TouchableOpacity>
</View>
))
) : (
<Text>no options</Text>
)}
</View>
);
};
const App = () => {
return (
<View>
<InputButton multipleOptions={['1', '2','3']} />
</View>
)
}
const styles = StyleSheet.create({})
export default App;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
},
button: {
margin: 3,
flex: 1,
backgroundColor: '#EEF6FA',
minHeight: 72,
borderRadius: 2,
justifyContent: 'center',
alignItems: 'center',
},
});
the error message is
Too many re-renders. React limits the number of renders to prevent an infinite loop.
or sometimes this
options.map is not a function
TypeError: options.map is not a function
at InputButton
(All kind of optimisations are welcome)
Thanks in Advance guys.
code demo https://snack.expo.io/#mansouriala/dac832
You put set state in every re-render so you get a loop. So you have two options use useEffect to just set state one time or set the first state directly.
https://snack.expo.io/ZvLQM9FEF
const InputButton = ({multipleOptions, likertScale, onPress}) => {
const [options, setOptions] = useState(likertScale?[...new Array(likertScale).keys()].map((i) => i):[ ...multipleOptions]);
return (
<View style={styles.container}>
{options ? (
options.map((option, i) => (
<View style={[styles.button]} key={`${i}`}>
<TouchableOpacity onPress={() => onPress(option)}>
<Text>{option}</Text>
</TouchableOpacity>
</View>
))
) : (
<Text>no options</Text>
)}
</View>
);
};
export default InputButton;
You have several issues here.
The first, which leads to Too many re-renders. React limits the number of renders to prevent an infinite loop. is because you're calling setOptions at each render, which triggers another render, etc… This is infinite loop, because when you're setting a state, React re-renders the component. To avoid that, you have to wrap your expression with useEffect and the correct dependencies.
React.useEffect(() => {
if (likertScale) {
setOptions([...new Array(likertScale).keys()].map((i) => i));
} else if (multipleOptions) {
setOptions(multipleOptions);
}, [multipleOptions, likertScale]);
This way, this expression would only run when multipleOptions or likertScale change.
https://reactjs.org/docs/hooks-reference.html#useeffect
The other problem is that InputButton props argument is wrong: you forgot the props destructuring. It should be const InputButton = ({ multipleOptions, likertScale, onPress }) => { /* body function */ }.
Finally, it's a bad practice to use an array index as a key prop, because array order could change. You should use a stable key, like the option value key={option}.
https://reactjs.org/docs/lists-and-keys.html#keys
so basically I am thinking to store the location data into asyncstorage whenever the onpress button is active. I already go through to the asyncstorage documentation however I still stuck and still need some hint and more example to do the coding.
can someone help me?
export default class index extends Component {
findCoordinates = () => {
Geolocation.getCurrentPosition(
(position) => {
const location = JSON.stringify(position);
this.setState({ location });
},
(error) => Alert.alert(error.message),
{
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 1000,
forceRequestLocation: true,
}
);
};
render() {
return (
<Container>
<ScrollView>
<View style={styles.top}></View>
<Card
containerStyle={{
borderRadius: 10,
marginTop: -30,
}}
>
<View>
<TouchableOpacity onPress={this.findCoordinates}>
<Text>Find My Coords?</Text>
<Text>Location: {this.state.location}</Text>
</TouchableOpacity>
</View>
</Card>
</ScrollView>
</Container>
);
}
}
Assuming that you have already imported the following:
import {AsyncStorage} from 'react-native';
Please add the following codes after you click the button :
AsyncStorage.setItem('storedlocation', this.state.location);
next time, if you want to get the asyncstorage value, say into the state of "retrievedlocation", please use the following codes:
getstoredlocationFunction = () => {
AsyncStorage.getItem('storedlocation').then(
value2 =>
this.setState({ retrievedlocation: value2 })
);
};
Have a nice day.
I have an issue that I don't quite understand.
I would like to display messages contained in an array using several flatlists. Then I will have to group them by date.
The messages actually correspond to a series of questions and answers from a chat where each message is recorded in an offline database (PouchDB is used). So I would like to display in an interface the questions that the user has answered, in short, I want to view the logs.
Here is the code:
const screen = Dimensions.get('screen');
const styles = StyleSheet.create({
logsView: {
backgroundColor: '#dddddd',
paddingLeft: 15,
paddingRight: 2,
height: '100%',
},
dateContainer: {
width: '75%',
padding: 1,
marginTop: 5,
},
dateContent: {
textAlign: 'center',
},
});
export default class ComponentPlanDetailsScreen
extends ComeoMeAbstractScreen<PropsType, StateType> {
static navigationOptions = {
title: µte('MyPlans'),
headerRight: (<View />),
};
constructor(props: PropsType) {
super(props);
this.IfeelMessagesBusiness = new IfeelMessagesBusiness();
this.state = {
currentDate: new Date(),
markedDate: moment(new Date()).format('YYYY-MM-DD'),
messages: [],
};
}
componentDidMount = () => {
// Get all messages from chat history
this.IfeelMessagesBusiness.getAllIfeelMessages().then((result: Object) => {
this.setState({ messages: result });
});
};
// Render each item of Flatlist
renderLogItem = ({ item }: Object) => {
console.log(`je passe renderlogitem ${JSON.stringify(item)}`);
return <LogItem message={item} />;
}
// Key for data in FlatList component
keyExtractor = (item: Object, index: number): string => index.toString();
render() {
const test = [
{
stringValue: 'Did you take some drugs ?',
isImage: false,
isQuestion: true,
questionNumber: 6,
author: {
id: 1,
avatar: 'http://image.noelshack.com/fichiers/2016/47/1480031586-1474755093-risitas721.png',
username: 'Dr Risitas',
},
loggedDateTime: '1552033946989',
},
];
const today = this.state.currentDate;
const day = moment(today).format('x');
return (
<View>
<Carousel
animate={false}
indicatorSize={10}
height={screen.height * 0.7
}
>
<View>
<ScrollView
style={styles.logsView}
>
<View>
{this.state.messages.map((ItemListByDate: Object): Array<Object> => {
console.log(`je passe array ${JSON.stringify([ItemListByDate])}`);
return (
<View key={ItemListByDate.loggedDateTime.toString()}>
<View style={styles.dateContainer}>
{ (parseInt(ItemListByDate.loggedDateTime, 10) + 172800000) <= parseInt(day.toString(), 10) ?
<Text style={styles.dateContent}>{moment(parseInt(ItemListByDate.loggedDateTime, 10)).format('DD-MM-YYYY')}</Text>
:
<Text style={styles.dateContent}>{moment(parseInt(ItemListByDate.loggedDateTime, 10)).fromNow()}</Text>
}
</View>
<View>
<FlatList
data={[ItemListByDate]}
renderItem={this.renderLogItem}
keyExtractor={this.keyExtractor}
ref={(ref: any) => { this.flatList = ref; }}
onContentSizeChange={(): any => this.flatList.scrollToEnd({ animated: true })}
onLayout={(): any => this.flatList.scrollToEnd({ animated: true })}
/>
</View>
</View>);
})
}
</View>
</ScrollView>
</View>
</Carousel>
</View>
);
}
}
The problem I don't understand is that the renderLogItem function to call the LogItem component is never called while the ItemListByDate array is displayed in the logs. No messages are displayed, I just have the grey background of the ScrollView component.
On the other hand, if I use the test array instead of this.state.messages with the map function, the message is displayed correctly in the interface and renderLogItem is called correctly.
I understand that when my component is called for the first time, the state is empty and the switch to the componentDidMount function will in my case trigger the update of the state and cause a re-render. This also causes the map function to call up and normally displays each message
Maybe it is due to a display problem, where the messages would be hidden because the initial state of the messages is empty ?
Thank you in advance for your help !
My first thought is that it has to do with the fact that this.IfeelMessagesBusiness.getAllIfeelMessages() is asynchronous. So the first time the render method is called, it tries to map òver undefined and it never updates.
Could you try doing a flatlist of Flatlist maybe ?
I have this code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: []
}
this._handleRenderItem = this._handleRenderItem.bind(this);
this._keyExtractor = this._keyExtractor.bind(this);
}
componentDidMount() {
let success = (response) => {
this.setState({ dataSource: response.data });
};
let error = (err) => {
console.log(err.response);
};
listarProdutos(success, error);
}
_keyExtractor = (item, index) => item._id;
_handleRenderItem = (produto) => {
return (
<ItemAtualizado item={produto.item} />
);
}
render() {
return (
<Container style={styles.container}>
<Content>
<Card>
<CardItem style={{ flexDirection: 'column' }}>
<Text style={{ color: '#323232' }}>Produtos atualizados recentemente</Text>
<View style={{ width: '100%' }}>
<FlatList
showsVerticalScrollIndicator={false}
data={this.state.dataSource}
keyExtractor={this._keyExtractor}
renderItem={this._handleRenderItem}
/>
</View>
</CardItem>
</Card>
</Content>
</Container>
);
}
}
export default Home;
The function _handleRenderItem() is being called twice and I can't find the reason. The first time the values inside my <ItemAtualizado /> are empty, but the second was an object.
This is normal RN behavior. At first, when the component is created you have an empty DataSource ([]) so the FlatList is rendered with that.
After that, componentDidMount triggers and loads the updated data, which updates the DataSource.
Then, you update the state with the setState which triggers a re render to update the FlatList.
All normal here. If you want to try, load the datasource in the constructor and remove the loading in the componentDidMount. It should only trigger once.
If you want to control render actions, you can use shouldComponentUpdate method.
For example:
shouldComponentUpdate(nextProps, nextState){
if(this.state.friends.length === nextState.friends.lenght)
return false;
}
it will break render if friends count not change.
I recreated the issue in this snack. https://snack.expo.io/B1KoX-EUN - I confirmed you can use shouldComponentUpdate(nextProps, nextState) to diff this.state or this.props and return true/false - https://reactjs.org/docs/react-component.html#shouldcomponentupdate docs say this callback should be used only for optimization which is what we're doing here.
I am trying to implement a todo app in react-native with features like addTodo, removeTodo, markCompleted todos. After adding todos, when I press on markComplete text, the listView is not re-rendering, if I reload the app it displays expected results. I am using Firebase database to fetch my todos from.
Basically, I am updating a property in my listView datasource when I click on markComplete. Everything is working fine expect the re-rendering of listView whenever I press markComplete or Completed buttons on UI. I have tried a few solutions suggested in related question, I couldnt get it working.
To be more specific: please look at code below comment // When a todo is changed. I am updating my datasource in those lines of code when I changes something in items array.
Below is my code and snapshot of app UI.
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
ListView,
Text,
View,
TouchableHighlight,
TextInput
} from 'react-native';
var Firebase = require('firebase');
class todoApp extends Component{
constructor(props) {
super(props);
var myFirebaseRef = new Firebase('[![enter image description here][1]][1]database URL');
this.itemsRef = myFirebaseRef.child('items');
this.state = {
newTodo: '',
completed: false,
todoSource: new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2})
};
this.handleKey = null;
this.items = [];
} // End of Constructor
componentDidMount() {
// When a todo is added
this.itemsRef.on('child_added', (dataSnapshot) => {
this.items.push({
id: dataSnapshot.key(),
text: dataSnapshot.child("todo").val(),
completedTodo: dataSnapshot.child("completedTodo").val()
});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
// When a todo is removed
this.itemsRef.on('child_removed', (dataSnapshot) => {
this.items = this.items.filter((x) => x.id !== dataSnapshot.key());
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
// When a todo is changed
this.itemsRef.on('child_changed', (dataSnapshot) => {
this.items.forEach(function (value) {
if(value["id"] == this.handleKey){
this.items["value"]["completedTodo"]= dataSnapshot.child("completedTodo").val()
}
});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
}
addTodo() {
if (this.state.newTodo !== '') {
this.itemsRef.push({
todo: this.state.newTodo,
completedTodo: this.state.completed,
});
this.setState({
newTodo : ''
});
}
console.log(this.items);
}
removeTodo(rowData) {
this.itemsRef.child(rowData.id).remove();
}
handleCompleted(rowData){
this.handleKey = rowData.id;
if(rowData.completedTodo){
this.itemsRef.child(rowData.id).update({
completedTodo: false
})
}
if(rowData.completedTodo == false){
this.itemsRef.child(rowData.id).update({
completedTodo: true
})
}
}
renderRow(rowData) {
return (
<View>
<View style={styles.row}>
<TouchableHighlight
underlayColor='#dddddd'
onPress={() => this.removeTodo(rowData)}>
<Text style={styles.todoText}>{rowData.text}</Text>
</TouchableHighlight>
<TouchableHighlight underlayColor='#dddddd' onPress={() => this.handleCompleted(rowData)}>
{rowData.completedTodo? <Text style={styles.todoText}>Completed</Text>:<Text style={styles.todoText}>MarkCompleted</Text>}
</TouchableHighlight>
</View>
<View style={styles.separator} />
</View>
);
}
render() {
return (
<View style={styles.appContainer}>
<View style={styles.titleView}>
<Text style={styles.titleText}>
My Todos
</Text>
</View>
<View style={styles.inputcontainer}>
<TextInput style={styles.input} onChangeText={(text) => this.setState({newTodo: text})} value={this.state.newTodo}/>
<TouchableHighlight
style={styles.button}
onPress={() => this.addTodo()}
underlayColor='#dddddd'>
<Text style={styles.btnText}>Add!</Text>
</TouchableHighlight>
</View>
<ListView
dataSource={this.state.todoSource}
renderRow={this.renderRow.bind(this)} />
</View>
);
}
} // Main Class End
Make sure to create new objects instead of updating the properties of existing objects.
If you want to update listView, create new objects instead of updating
the properties of existing objects.
The below code resolved a similar issue on Github.
let newArray = oldArray.slice();
newArray[indexToUpdate] = {
...oldArray[indexToUpdate],
field: newValue,
};
let newDataSource = oldDataSource.cloneWithRows(newArray);
For more detailed explanation, This answer might help you.