React Native - Navigator inconsistent across different methods - android

I am creating my first React Native App. I am trying to use the navigator object for navigating between different views.
In the below code snippet.
The openRecipe method written works perfectly but the goBack method throws an exception saying
undefined is not an object(evaluating this.props.navigator)
I haven't added any props to the Component Class, which I initially thought to be a problem, but since the OpenRecipe method works fine, I am confused on why goBack is throwing on exception which has the same method body as the openRecipe method.
If there were an issue with not including dependencies then it should have been consistent across both the methods.
Once it is sorted out,I am planning to use this.props.navigator.pop() to go back to the previous page.
openRecipe(data){
this.props.navigator.push({
id: 'RecipePage',
name: 'Recipe',
});
}
goBack(){
Alert.alert(
"Here Back!",
)
this.props.navigator.push({
id: 'RecipePage',
name: 'Recipe',
});
}
render() {
return (
<View style={styles.container}>
<View style={styles.row}>
<Text style={styles.title}>Recipe</Text>
<TouchableHighlight onPress={this.goBack}>
<Text style={styles.title} >BACK</Text>
</TouchableHighlight>
</View>
<ListView
dataSource={this.state.dataSource}
renderRow={(data) =>
<TouchableHighlight onPress={() => this.openRecipe(data)}>
<View style={styles.article_container} >
<Text style={styles.article_title} >{data.title}</Text>
<Image style={styles.article_img}
source={{uri: data.image_link}}
/>
</View>
</TouchableHighlight>
}
/>
</View>
);

If your component is implemented as an ES6 class, the goBack method is not automatically bound to the this instance of your object, which React.createClass does automatically. The solution is to either pass in a "fat arrow" lambda as the onPress prop (e.g onPress={() => this.goBack()}), which will bind this to the value it has where the lambda is defined, or to bind it explicitly with onPress={this.goBack.bind(this)}
To elaborate, now that I'm not on a phone keyboard...
In javascript, the value of this depends on the context in which a function (or method) is called, not where it's defined. When a function is a property of an object (a method), and it's invoked as such, this has the value you probably expect; it's the parent object that contains the method.
const person = {
name: 'Shaurya',
sayHello() {
return "Hi " + this.name
}
}
person.sayHello() // -> "Hi Shaurya"
But if I store the sayHello function in a variable and call it from "outside" the object's context, the value of this will depend on where you're calling the function from. If you're running at the global scope (e.g inside a global function, or at a node repl), this will be the global object (where language builtins like Math live). Unless that happens to have a name property, you'll get undefined for this.name:
let sayHi = person.sayHello
sayHi() // -> "Hi undefined"
You can use the .apply method of the Function type to set the value of this to something else temporarily:
sayHi.apply(person) // -> "Hi Shaurya"
sayHi.apply({name: "Yusef"}) // -> "Hi Yusef"
sayHi() // -> still "Hi undefined"
Or, you can use .bind to set the value of this and make it persist:
var sayHiToPerson = person.sayHello.bind(person)
sayHiToPerson() // -> "Hi Shaurya"
The "fat arrow" lambdas introduced in ES6 capture the value of this, and no matter where you invoke it, this will have the same value as it did when the lambda was defined. That's why your second onPress handler works but the first one doesn't. Inside the body of () => this.openRecipe(data), this gets bound automatically to the value it had inside the .render method. But when you just pass this.goBack you lose that context, and this has a different value when the function is invoked by the event handling system.

Related

RadListView not responding to new items in an ObservableArray

I'm trying to render a list of images, using RadListView. Being that i had some bizarre behavior when the data was coming from a normal array, i decided to try ObservableArray, as recommended in the docs.(specifically tns-vue)
The problem is, that pushing a new item to the model, doesn't update the view. The item is there, but nothing is shown.
This is my RadListView:
<RadListView layout="grid" ref="newImages" for="image in newImages">
<v-template>
<ImageComponent
showDate="false"
:onLongPress="()=>{onLongPress(image)}"
:image="image"
></ImageComponent>
</v-template>
</RadListView>
The "newImages" array:
data() {
return {
newImages: new ObservableArray([]),
};
}
I add items to the array, using the camera plugin:
openGallery() {
var that = this;
console.log(that.newImages.length);
var context = imagepicker.create({ mode: "multiple" }); // use "multiple" for multiple selection
context
.authorize()
.then(function() {
return context.present();
})
.then(function(selection) {
const images = [];
selection.forEach(function(selected) {
const image = that.createNewFileSchema(selected._android);
images.push(image);
});
that.newImages.push(images)//This adds the images to the array, but UI doesn't respond to the change.
})
.catch(function(e) {
alert(e);
});
},
What could be the problem here?
Your pushing arrays to a data arrays, to make the virtual DOM notice these changes, you probably wan't to use a deep watcher, calling a method returning the updated array.
You would have the same problem with Objects, but then you would be able to use:
this.$set(<object>, <key>, <value>)
I'm unsure if there is a better way for arrays, but you could try a watcher as said
watch: {
newImages: {
handler: function(<value>, <oldValue>) {},
deep: true
}
}
UPDATED - You can use this.$set for arrays
https://v2.vuejs.org/v2/api/#Vue-set
/* this.$set(<Array>, <Index>, <Value>) */
this.$set(this.newImages, this.newImages.length, [...newArrWithImages])
This guy explains reactively updating arrays: Vuejs and Vue.set(), update array

How to order data on the fly

I'm a beginner with react native or firebase so I don't really know how to explain this but I have no idea on how to order received data from the database.
Let's say I have a database:
appname
items
-some_random_generated_string
-name: "someString"
-value: "999"
-type: 0
-some_random_generated_string
-name: "someString"
-value: "999"
-type: 0
I've tried already with all order types and also setting .indexOn rule but haven't come to a solution. Also tried adding an id and then order with it and didn't come to a solution.
I guess this is accessing the database to get items so I also tried ordering them on the same line but nothing worked except limiting the amount of data.
let itemsRef = db.ref('items');
then I have:
componentDidMount() {
itemsRef.on('value', snapshot => {
let data = snapshot.val();
let items = Object.values(data);
this.setState({ items });
});
}
and I'm adding like this:
let addItem= (item, value, type) => {
db.ref('/items').push({
name: item,
value: value,
type: type,
});
};
Basically what I want to achieve is to display data in reversed order than it was added so the last one added would be shown on the top.
You could do it in two ways.
First simply call .reverse() on your current array. If you call the push method to add new items, usually the key that's assigned to each child garanties they are stored in chronological order. Therefore, calling it as such should be good enough:
componentDidMount() {
itemsRef.on('value', snapshot => {
let data = snapshot.val();
let items = Object.values(data);
items.rerverse();
this.setState({ items });
});
}
Though i don't know if let items = Object.values(data); garanties on every browser that your data are ordered as intended. By the way Object.values() opposed to Object.keys() is not supported on many old browsers, but if you're using it in ReactNative then it's fine.
If you want to be fully sure it's properly ordered, rewrite it as such:
componentDidMount() {
itemsRef.on('value', snapshot => {
let items = [];
// forEach always send you the children in the right order
snapshot.forEach(snap => {
items.push(snap.val())
})
items.rerverse();
this.setState({ items });
});
}
Finally another way to do it if you don't trust firebase's auto generated ids, is to add objects as such:
let addItem = (item, value, type) => {
db.ref('/items').push({
name: item,
value: value,
type: type,
timestamp: new Date().getTime()
});
};
and to use firebase's function orderByChild when you fetch the data, using timestamp as the child, and then .reverse() it again like we did previously.

MvxAutoCompleteTextView dropdown re-opens on every keypress

I have a problem whereby upon every keypress from the user, the AutoCompleteTextView quickly hides and re-appears again (with an updated set of values).
Please suggest where my problem could be and whether you see any other problems with the below code.
Binding in the View:
bindingSet
.Bind(emailAutoCompleteTextView)
.For(t => t.Text)
.To(vm => vm.Email);
bindingSet
.Bind(emailAutoCompleteTextView)
.For(t => t.PartialText)
.To(vm => vm.CurrentEmailEntry);
bindingSet
.Bind(emailAutoCompleteTextView)
.For(t => t.ItemsSource)
.To(vm => vm.CurrentEmailAutoCompleteSuggestions);
AXML Layout:
<mvvmcross.droid.support.v7.appcompat.widget.MvxAppCompatAutoCompleteTextView
android:id="#+id/EmailAutoCompleteTextView"
android:layout_marginTop="#dimen/PaddingBetweenUserInputFields"
android:completionThreshold="1"
android:inputType="textEmailAddress" />
View Model Code:
private string _currentEmailEntry;
public string CurrentEmailEntry
{
get
{
return _currentEmailEntry;
}
set
{
_currentEmailEntry = value;
if (value == string.Empty)
{
_currentEmailEntry = null;
}
CurrentEmailAutoCompleteSuggestions = _emailAutoCompleteList
.Where(email => email.StartsWith(_currentEmailEntry, StringComparison.OrdinalIgnoreCase))
.ToArray();
RaisePropertyChanged(nameof(CurrentEmailEntry));
}
}
private static readonly string[] _emailAutoCompleteList = {"Gordon", "Gordy", "Go", "Freeman", "Is", "Alive"};
private IList<string> _currentEmailAutoCompleteSuggestions = _emailAutoCompleteList.ToList();
public IList<string> CurrentEmailAutoCompleteSuggestions
{
get { return _currentEmailAutoCompleteSuggestions; }
set
{
if (ReferenceEquals(_currentEmailAutoCompleteSuggestions, value))
return;
_currentEmailAutoCompleteSuggestions = value;
RaisePropertyChanged(nameof(CurrentEmailAutoCompleteSuggestions));
}
}
I use MvvmCross 4.0 (upgrade is not an option).
NB: I tried using an ObservableCollection instead of an IList and remove/add items to it (not re-assign the collection itself) but after that the setter for CurrentEmailEntry stopped receiving values after the user typed the first character into the text view. The code inside MvxFilteringAdapter seems to be stuck waiting on a reset event.
Since no one answered either here or on the MvvmCross Xamarin Slack channel I eventually discovered the solution myself.
It was the re-creation of the list bound to the ItemsSource that led to the strange behaviour in question.
The ObservableCollection with Clear()/Add() instead of re-creating was indeed the way to go. The stuck behaviour (waiting on the reset event) described in the last question paragraph was caused by the absence of proper thread dispatching (my application is multi-threaded).
As soon as I wrapped my observable collection with a proxy collection that always raised the CollectionChanged event on the UI thread, the problem disappeared.
Leaving this here for the benefit of future generations.

React Native: Integration With Existing Apps

I'm new to react and I followed the tutorial about integrating existing apps open in the React Native Docs.
private ReactRootView mReactRootView;
.......
Bundle launchOptions = new Bundle();
launchOptions.putBoolean("test", true);
//mReactRootView.startReactApplication(mReactInstanceManager, "ThirdAwesomeComponent", launchOptions);
mReactRootView.startReactApplication(mReactInstanceManager, "ThirdAwesomeComponent", null); // Actual example
Is there a way to read launchOptions in the HelloWorld Component at index.android.js?
Also I have two activities from where I need to call the react native daemon and want to render two different layouts returned by the server.
How can I do that since currently I only have one:
AppRegistry.registerComponent('HelloWorld', () => HelloWorld);
The best way to do is doing something like,
Redirect to App.js from the index page using
AppRegistry.registerComponent("App",()=>App)
This will redirect to app
Then for rendering two different scenes based on server output. You can create a state variable and initialize it to be the default state.
in the render function of you component you can then check the state value and assign the layout as per your necessity.
Use something like
export default Class App extends Component{
constructor(props){
super(props)
this.state{
data1:false,
data2:true,
loaded:false,
}
}
//do all the fetching data to server here
componentWillMount(){
//after fetching the data to server
change the state as
this.setState({
data1:true,
data2:false,
loaded:true
})
}
render({
if(this.state.loaded && this.state.data1){
return(
//layout which you want to return
)
}else if( this.state.loaded && this.state.data2){
return(
//second layout code
)
}else{
return(
//can create a loading spinner here
<Text>Loading.....</Text>
)
}
})
}
Hope this helps
Cheers
Your launching options will be passed to the constructor of your component as props.
Just implement the constructor
constructor(props){
super(props)
// do stuff to pops dictionary
}

How to pass array of View/Component in react native?

I am making App framework in React Native. Framework consist of some basic screens like login screen, Tab screen. The purpose of the framework is to provide fundamental design to new app which will be developed from grounds up.
Tab Screen
As we know each Tab will have individual view to display. I want to make tabscreen totally customizable. It means based on passed View list and tab list, it should render.
For that i need to pass Array of View/Component as a prop to TabScreen.
How can I make array of View/Component?
How to pass array as props to TabScreen?
below is the code of TabScreen
'uses strict'
import {StyleSheet, View, Text, Image} from 'react-native';
import React, {Component, PropTypes} from 'react';
import {IndicatorViewPager, PagerTabIndicator} from 'rn-viewpager';
export default class TabScreen extends Component {
constructor (props) {
super(props)
}
static propTypes = {
views : PropTypes.arrayOf(PropTypes.instanceOf(View)).isRequired,
tabs: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string,
iconSource: Image.propTypes.source,
selectedIconSource: Image.propTypes.source
})).isRequired,
}
render() {
return (
<View style={{flex:1}}>
<IndicatorViewPager
style={{flex:1, paddingTop:20, backgroundColor:'white'}}
indicator={<PagerTabIndicator tabs={this.props.tabs} />}>
{views}
</IndicatorViewPager>
</View>
);
}
}
module.exports = TabScreen;
Please help
You don't need to pass an array of react native components, you have to use the children of the component, like this:
In the render method of your upper-level component:
<TabScreen>
<View><Text>First tab</Text>
<View><Text>Second tab</Text></View>
{/* And so on... */}
</TabScreen>
Then, in your TabScreen component, just do:
render() {
return (
<View style={{flex:1}}>
<IndicatorViewPager
style={{flex:1, paddingTop:20, backgroundColor:'white'}}
indicator={<PagerTabIndicator tabs={this.props.tabs} />}>
{this.props.children}
</IndicatorViewPager>
</View>
);
}
In any case, in response to your questions:
How can I make array of View/Component?
Just as any other array. For instance, with a map:
let elements = ['First Tab', 'Second Tab'].map((text)=> <View><Text>{text}</Text></View>))
How to pass array as props to TabScreen?
Arrays are no different to any other data type when it comes to props (any variable can be passed as a prop, unless it has some sort of validation mechanism, in which case it will raise a warning).

Categories

Resources