I would like to have two text fields:
one that accepts a title
another that accepts a body (i.e. more text)
...and a submit button:
that saves the title and body that was entered, when clicked
I have researched TextInput, AsyncStorage, TouchableHighlight and Navigator components as well as a bunch of react-native tutorials. I can't seem to find any consistency - not even from the react-native docs.
Here is what I have so far:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
AsyncStorage,
TextInput,
TouchableHighlight
} from 'react-native';
class PostAndSave extends Component {
constructor(props) {
super(props);
this.state = {
messageTitle: '',
messageBody: ''
}
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Walker app
</Text>
<TextInput
placeholder="Title"
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChange={(event) => this.setState({messageTitle: event.nativeEvent.text})}
value={this.state.messageTitle} />
<TextInput
placeholder="Body"
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChange={(event) => this.setState({messageBody: event.nativeEvent.text})}
value={this.state.messageBody} />
<TouchableHighlight onPress={this._onPressButton} style={styles.button}>
<Text style={styles.buttonText}>See all posts</Text>
</TouchableHighlight>
</View>
);
}
}
// styles here
AppRegistry.registerComponent('PostAndSave', () => PostAndSave);
I can type into the input fields but cannot figure AsyncStorage out, or how to post new messages as opposed to the overwriting the existing one. I'm mainly looking for help in that area - below I have posted my goal incase the question of why I want to do this comes up.
Goal:
The saved 'post' should then be printed to a view, where it can be pressed (tapped?) to display the contents of the body.
Each time a title and body are submitted they should be saved as a new 'post' and not overwritten.
If you want to use Async for this you'll need a function to save the data:
_onPressButton () {
// Get the data
let title = this.state.messageTitle
let message = this.state.messageBody
// Retrieve the existing messages
AsyncStorage.getItem('messages', (res) => {
var messages
// If this is the first time, set up a new array
if (res === null) {
messages = []
}else {
messages = JSON.parse(res)
}
// Add the new message
messages.push({
title: title,
message: message
})
// Save the messages
AsyncStorage.setItem('messages', JSON.stringify(messages), (res) => {})
}
}
And you'll want to bind this to your instance:
<TouchableHighlight onPress={this._onPressButton.bind(this)} style={styles.button}>
And to retrieve your messages for use later:
AsyncStorage.getItem('messages', (res) => {
this.setState({
messages: res
})
})
Related
I have an application created with Expo and the wing which has a contact form from which to send inquiries.
I have started with the application already built, I am just setting my data.
The problem is that the sent queries do not reach the mail that I have established in the configuration, in a file (config.php) so that the messages must arrive in smtp mode
This is my server configuration:
$emailConfig = array(
'address' => 'xxxxxxx',
'password' => 'xxxx',
'name' => 'jose',
'smtp_host' => 'smtp.xxxx.es',
'smtp_port' => '587',
'smtp_encrypt' => 'tls'
);
The application, the debugging console shows me the following when I click SEND:
Toast is not defined
* application/components/PlaceFav.js:82:10 in render
- node_modules/react-native/node_modules/promise/setimmediate/core.js:37:11 in tryCallOne
- node_modules/react-native/node_modules/promise/setimmediate/core.js:123:14 in setImmediate$argument_0
- ... 8 more stack frames from framework internals
The file referenced by the console output is as follows, the line :
renderItem={({item, index}) =>
import React, {Component} from 'react';
import * as firebase from 'firebase';
import { NavigationActions, StackNavigator, withNavigation} from 'react-navigation';
import{AsyncStorage, TouchableOpacity, Dimensions, View, Image, ScrollView, FlatList} from 'react-native';
import Icon from 'react-native-vector-icons/SimpleLineIcons';
import { Container, Body, Thumbnail, Text, List, Right, ListItem} from 'native-base';
import ConfigApp from '../utils/ConfigApp';
import FavListEmpty from './FavListEmpty';
import Strings from '../utils/Strings';
var styles = require('../../assets/files/Styles');
var {height, width} = Dimensions.get('window');
class PlaceFav extends React.Component {
constructor(props) {
super(props);
this.state = {
places: []
}
}
componentDidMount () {
this.fetchPlaces();
}
PlaceDetails (item) {
const navigateAction = NavigationActions.navigate({
routeName: 'PlaceDetailsScreen',
params: {item}
});
this.props.navigation.dispatch(navigateAction);
}
renderFooterPlaces = () => {
const places = this.state.places
if (places.length != 0) return null;
return (
<FavListEmpty/>
);
};
removePlace = async (place_id) => {
try {
var user = firebase.auth().currentUser;
uid = user.uid;
const places = await AsyncStorage.getItem('places');
let placesFav = JSON.parse(places);
placesItems = placesFav.filter(function(e){ return e.place_id !== place_id && e.userId == uid })
await AsyncStorage.setItem('places', JSON.stringify(placesItems));
this.setState({
...this.state,
places: placesItems || []
});
} catch(error) {
}};
render () {
return (
<List>
<ListItem itemDivider>
<Text>{Strings.ST1}</Text>
</ListItem>
<FlatList
data={this.state.places}
refreshing="true"
renderItem={({item, index}) =>
<ListItem style={{paddingLeft: 0, marginLeft: 0, backgroundColor:'#FFF', opacity: 1, borderColor: 'rgba(0,0,0,0.05)', borderBottomWidth: 1}} onPress={() => this.PlaceDetails(item)} >
<Thumbnail rounded size={80} source={{ uri: ConfigApp.URL+'images/'+item.place_image }} style={{paddingLeft: 10, marginLeft: 10}} />
<Body style={{paddingLeft: 0, marginLeft: 0}}>
<Text numberOfLines={2} style={{fontSize: 14, marginBottom: 3}}>
{item.place_name}
</Text>
</Body>
<Right>
<TouchableOpacity onPress={this.removePlace.bind(this, item.place_id)} activeOpacity={1}>
<Text note>
<Icon name="close" style={{fontSize: 19}}/>
</Text>
</TouchableOpacity>
</Right>
</ListItem>
}
keyExtractor={(item, index) => index.toString()}
ListFooterComponent={this.renderFooterPlaces}
/>
</List>
)
}
async fetchPlaces () {
var user = firebase.auth().currentUser;
uid = user.uid;
let placesJSON= await AsyncStorage.getItem('places');
let placesFav = JSON.parse(placesJSON);
placesItems = placesFav.filter(function(e){
return e.userId == uid
})
const placesArray = placesItems || [];
this.setState({
...this.state,
places: placesArray
});
}
}
export default withNavigation(PlaceFav);
I do not have much knowledge yet in React in Javascript and php, I do not know what this error means and I have searched for answers without success.
I don't know if what I show is enough for you to help me
I finally discovered the mistake, a beginner's mistake
I explain it for if another user happens the same as me.
The problem was that I was not editing the Backend code. I mean that I am working on the project locally and the Backend is on a server.
I modified the code in my local files, but it did not upload the updates to the server
This was why the messages from the application were not sent
I hope this can help someone else, and it makes me think that you have to check all the possible errors before looking for help and solution.
Thanks to this wonderful site
Description
I implement a pull request using RequestController in React Native, every time I did pull to refresh the same data keep adding on in Flat list over and over. I implemented pull request not inside the Flat list but on the ScrollView which wrapped the FlatList.
Actions
import React, { Component } from 'react';
import { View, StyleSheet, Text, Button, Modal, Dimensions, ScrollView, TextInput, TouchableOpacity, StatusBar, Image, Platform, TouchableNativeFeedback,FlatList, ImageBackground, RefreshControl } from 'react-native';
import axios from 'axios';
class HomeScreen extends Component{
state = {
refreshing: false,
}
componentDidMount(){
this.searchApi();
}
searchApi = async() => {
const response = await axios.get('http://73udkYid.ngrok.io/api/v1/products',{
headers: {
"x-auth-token":"eyJfaWQiOiI1ZdfjzZmM4YjIwYjBjZDQyMmJkNzUiLCJpYXQiOjE2MD"
}
}
);
this.setState({results: [...this.state.results, response.data]});
}
_onRefresh = () => {
this.setState({refreshing: true});
this.searchApi().then(() => {
this.setState({refreshing: false});
});
}
render(){
let finalGames = [];
this.state.results.forEach(obj => {
Object.keys(obj).forEach(key => {
finalGames.push(obj[key]);
});
});
return (
<ScrollView style={{flex: 1,backgroundColor: 'white',}}
refreshControl = {
<RefreshControl
refreshing = { this.state.refreshing }
onRefresh={this._onRefresh}
/>
}
>
<FlatList
data = { finalGames }
keyExtractor = {(item, index) => index.toString()}
renderItem = { ({item: itemData}) => {
if(itemData.empty == true){
return <View style = {[styles.item,styles.itemInvisible]}/>
}
return (
<View style = {{ flex: 1, margin: 4}}>
<View style = {styles.item}>
<TouchableOpacity
onPress = {() => {
this.setState({ viewController: this.state.viewController++ })
this.props.navigation.navigate(
"ProductDetail", {
itemDataDetail: itemData,
businessname:this.props.navigation.state.params.businessname,
viewController: this.state.viewController,
})
}}>
<View>
<ImageBackground
source={{ uri: itemData.photo }}
style={{ width:'100%',aspectRatio: 1, borderRadius: 15, borderWidth:1, borderColor:"#FAFAFA", overflow: 'hidden'}}>
</ImageBackground>
<View style = {{ margin: 5}}>
<Text style = {{color: '#2E2E2E', fontWeight:"bold"}} numberOfLines={1}>{itemData.item}</Text>
<Text style = {{color: '#2E2E2E', fontSize: 12, fontWeight:"normal", alignSelf: 'flex-start'}} numberOfLines={1}>Available now | Sold out</Text>
<Text style = {{color: 'white', fontSize: 18, fontWeight:"bold", backgroundColor:"#DE1F38", alignSelf: 'flex-start', paddingHorizontal: 10,paddingVertical:2,borderRadius: 8,overflow: 'hidden', marginTop: 5}} numberOfLines={1}>${itemData.price}</Text>
</View>
</View>
</TouchableOpacity>
</View>
</View>
</ScrollView>
);
}}/>
}
Output
Data duplicated every time new pull refresh triggered
I assume your api-call returns the whole list of products
This line concat api-response-data to the the list of products you already have in your component-state
this.setState({results: [...this.state.results, response.data]});
Try this instead...
this.setState({ results: response.data });
You should replace your data instead of concatenating. Use:
this.setState({ results: response.data });
Also, you should use FlatList 'onRefresh' prop to implement refresh functionality instead of using an extra ScrollView on the parent.
Oh I found a way. I just need to do this.
this.setState({results: [response.data]});
I was facing the same problem as you,
When I refreshed, the data was (data)+[(data)+(new_data)].
What happens here is that data is added to the array of this variable: results.
To prevent this you must first clear this variable: results.
So your code will look like this.
state = {
refreshing: false,
results : [],
}
when API runs, this array will filled results[{some_data},{some_data},{some_data},..],
While you refresh->
1st: The results will Empty,
2nd: reassign that array with newly added data from API.
_onRefresh = () => {
this.setState({results: []});
this.setState({refreshing: true});
this.searchApi().then(() => {
this.setState({refreshing: false});
});
}
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 am making a To Do list app using React Native, where I add events to a FlatList and then have a button that removes that event once it it finished. So far this is what I have. It seems very hacky to me, but most of it works.
import React from 'react';
import { StyleSheet, Text, View, TextInput,TouchableOpacity, FlatList} from 'react-native';
export default class App extends React.Component {
constructor(props){
const data = [];
super(props);
this.state ={
text: 'Enter activity here',
data: data,
color: true,
currNum: 0,
}
}
updateText(){
this.setState({data:this.state.data.concat({key:this.state.text,index:this.state.currNum})});
this.state.currNum++;
}
removeText(item){
this.setState({data:this.state.data.pop(item.index)});
this.state.currNum--;
}
render() {
return (
<View style={styles.container}>
<Text></Text>
<View style = {{flexDirection:'row',justifyContent:'flex-end'}}>
<TextInput style = {{fontSize:30,borderColor:'black', flex:1, marginTop:20}} onChangeText = {(text) => this.setState({text})}value = {this.state.text}/>
<TouchableOpacity style = {{marginTop:20}}onPress = {()=>(this.updateText())}>
<Text>Add to list</Text>
</TouchableOpacity>
</View>
<View style = {{flex:1, flexDirection:'row'}}>
<FlatList
data = {this.state.data}
extraData = {this.state}
renderItem = {({item}) => <View><Text style={styles.text} >{item.key}</Text><TouchableOpacity onPress = {() => this.removeText(item)}><Text>Remove</Text></TouchableOpacity></View>}
/>
</View>
</View>
);
}
}
When I press the "remove" button, I delete an element from the list of data that the FlatList uses. However, whenever I do this I get an error saying "Tried to get frame for out of range index NaN". Is there a way for me to regularly update and remove a FlatList, and to re-render the FlatList once I have removed an item? I have tried using the extraDate prop, but it hasn't worked. I believe I am using it wrong though. Thank you for all the help.
I think you should use .filter
removeText(item){
this.setState({
data: this.state.data.filter((_item)=>_item.key !== item.key)
});
}
And what this.state.currNum - is for?
use this instead you shouldn't mutate this.state and Array.prototype.pop() mutates it
removeText(item, index){
this.setState({data: [
...this.state.data.slice(0, index),
...this.state.data.slice(index + 1)
]});
this.state.currNum--;
}
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.