Can't access this.props.navigator - react-native

I've been creating my views this way:
var SettingsView = React.createClass({
render: function() {
return (
<View>
<TouchableHighlight onPress={this.donePressed}>
//left out styling code for simplicity
</TouchableHighlight>
</View>
);
},
donePressed: function() {
this.props.navigator.pop();
},
});
This is working fine. donePressed is an event handler that's hooked up with a TouchableHighlight in render (not shown).
Today I tried to refactor it to this:
class SettingsView extend React.Component {
render() {
//same stuff as above
}
donePressed() {
this.props.navigator.pop();
}
}
This rendered the settings screen as normal. But when donePressed triggered, screen turned red saying they can't find this.props.navigator.
I'm not sure what is going on here, please help me out!
Edit:
This screen is brought up via another touch event handler from a different page via this method:
settingsTapped: function() {
this.props.navigator.push({
name: 'SettingsView',
component: SettingsView,
})
},
I've also tried to add passProps: this.props.navigator within settingsTapped too. What I'm most confused is why changing the way I create the class could suddenly make this.props.navigator invisible.

I think the reason it wasn't working is that because you are using Classes as opposed to the createClass function, this is no longer bound to the correct scope. You may need to use an es6 fat arrow function to get this lexically bound to the correct scope, like so:
onPress={ () => this.donePressed() }
You could also do this to get it to work:
onPress={ this.donePressed.bind(this) }

Related

React native with redux is laggy on dispatch

In my app, I have a function which calls every 2s a bluetooth command to ask the current temperature of a device with a setInterval function.
The bluetooth response is given by monitored function. I use react native-ble-plx library for that.
I have no problem with this process.
The temperature is returned via a property which is dispatched via redux in an action file.
But when I "dispatch" (via redux) the function to my screen, I have a short interrupt which causes a laggy/jerky behavior. In my case, I have a slide to unlock button, and on my device when the dispatch is call, the touch operation is interrupted, and become not intuitive and annoying. It's difficult to explain the problem, but my question is simple, how I have to set react-redux not to be laggy, or not interrupt current user interaction on redux dispatch ?
My app, is based on this project structure (for react-redux with Ble) : https://github.com/momolarson/BLEServiceDiscovery
Environement:
react-native: 0.63.3
react-native-ble-plx: 2.0.2
react-redux: 7.2.1
This is pseudo code of my app (the code is more longer, but I have excluded all other by remove them) :
HomeScreen.js
import stuff[...]
class HomeScreen extends Component {
componentDidMount() {
this.timer = setInterval(() => {
this.props.readTemp();
}, 2000);
}
render() {
const { value } = this.state
return (
<>
<ScrollView>
<Text>{this.props.temperatture}"></Text>
<Slide2Unlock/>
</ScrollView>
</>
);
}
}
function mapStateToProps(state) {
return {
temperature: state.temperature,
};
}
const mapDispatchToProps = dispatch => ({
readTemp: () => bluetooth.readTemp(),
})
export default connect(mapStateToProps, mapDispatchToProps())(HomeScreen);
redux's action file : actionBt.js (my file is based on this https://github.com/momolarson/BLEServiceDiscovery/blob/master/actions/index.js)
[...]
device.monitorCharacteristicForService(
characteristicData.serviceUUID,
characteristicData.uuid,
(error, characteristic) => {
if (characteristic != null && characteristic.value != null) {
dispatch(formatTemperature(characteristic.value));
}
},
);
thanks for your help
Update 1
I make a specific version of my app, without bluetooth, just the slide to unlock module and a watcher with setInterval, and still have a laggy behavior, when the state is dispatched. I have done tests with button only, when I tap then show the value via dispatch, it's still the same trouble.
this my test code, index.js (redux action file)
export const readTemp = () => {
return (dispatch, getState, DeviceManager) => {
const state = getState();
console.log("READ TEMP");
dispatch(temperatureSensor( Math.random(0,9) ))
}
}
function BLEservices(BLEServices) {
setInterval(() => {
BLEServices.readTemp();
}, 2500);
return (
<SafeAreaView style={styles.container}>
<Slider
childrenContainer={{ }}
onEndReached={() => {
console.log('REACHED')
}}
containerStyle={{
height:40,
margin: 8,
backgroundColor: "#EEEEEE",
overflow: 'hidden',
alignItems: 'center',
justifyContent: 'center',
width: '50%',
}}
sliderElement={
<Text style={{color:"#FFF"}}>TEST</Text>
}
>
<Text style={{color: "#D5BD9E"}}>unlock</Text>
</Slider>
<Text>Temperature: {BLEServices.temperatureSensor}</Text>
</SafeAreaView>
);
}
thanks for your advice, and your help
Update 2
Solution found, see my answer below. The problem was type of var user in dispatch and some side effect due to previous test I have done on app and not clean them.
I solved my problem, by finding multiple var who are contains objects. I have a var which contain four attributes, I update and use one of them. And this object was update by my watcher. When I dispatch object to get a part of this object, I have to read the whole object, and this one is fully updated by my watchern which cause laggy render. So i have splitted that, to update only per var.
Another thing I've done, I split my interface elements in multi component, before, I has a lot of code in one screen, because I didn't need to reuse them elsewhere.

React Native components seem to be sharing a state

I'm having an issue with React-native where I have a component TouchTimer which uses an AnimatedTimer component. This timer is supposed to start and stop when it is tapped, which it does, however all of the TouchTimer components I add to a page will start and stop whenever any of them are tapped, rather than only affecting the tapped component.
Below is a snippet of my component:
TouchTimer.tsx
export class TouchTimer extends React.Component<TouchTimerProps> {
state: {
...
paused: boolean,
}
constructor(props) {
super(props);
...
this.state = {
...
paused: true,
}
}
startStop() {
this.setState({paused: !this.state.paused});
}
render() {
const { time } = this.props;
return (
<TouchableHighlight onPress={() => this.startStop()}>
<View>
<AnimatedTimer
...
time={time}
pause={this.state.paused}
/>
<View style={styles.timeContainer}>
<Text style={styles.time}>{this.state.remaining}</Text>
</View>
</View>
</TouchableHighlight>
)
}
}
And here is a snippet of the screen containing these components:
Details.tsx
import { TouchTimer } from '../components/TouchTimer';
...
export class RecipeDetailsScreen extends React.Component<NavigationInjectedProps> {
...
{this.state.steps.map(step => (
<List.Item
key={step.id}
title={"Step " + step.index}
style={styles.step}
description={step.short_desc}
right={() => (step.time > 0 &&
<TouchTimer
time={step.time * 60000}
/>
)}
/>
)
}
I have tried wrapping the TouchTimer components in a View and changing the paused boolean to a prop, to no avail.
I have also tested to see if this issue appears when the components are not siblings, and when they are not produced as the result of a callback, and the issue still persists in both these cases.
If anybody has any advice or answers on how to make these timers independent I would very much appreciate it!
Curiously that component seems to be implemented with a global pauseFlag that applies to all component instances. See https://github.com/dalisalvador/react-native-animated-timer/blob/master/src/Components/AnimatedTimer.js#L34
So I don't think you're doing anything wrong here, this is a limitation of the library code that is coupling all instances of your timer to the same pauseFlag value.

react-native scrollView - scrollToEnd - on Android

I'm trying to call a function that will fire upon onFoucs on TextInput that will scroll the scrollView all the way down (using scrollToEnd())
so this is my class component
class MyCMP extends Component {
constructor(props) {
super(props);
this.onInputFocus = this.onInputFocus.bind(this);
}
onInputFocus() {
setTimeout(() => {
this.refs.scroll.scrollToEnd();
console.log('done scrolling');
}, 1);
}
render() {
return (
<View>
<ScrollView ref="scroll">
{ /* items */ }
</ScrollView>
<TextInput onFocus={this.onInputFocus} />
</View>
);
}
}
export default MyCMP;
the component above works and it does scroll but it takes a lot of time ... I'm using setTimeout because without it its just going down the screen without calculating the keybaord's height so it not scrolling down enough, even when I keep typing (and triggering that focus on the input) it still doesn't scroll all the way down.
I'm dealing with it some good hours now, I did set the windowSoftInputMode to adjustResize and I did went through some modules like react-native-keyboard-aware-scroll-view or react-native-auto-scroll but none of them really does the work as I need it.
any direction how to make it done the right way would be really appreciated. thanks!
Rather than using a setTimeout you use Keyboard API of react-native. You add an event listener for keyboard show and then scroll the view to end. You might need to create some logic on which input is focused if you have more than one input in your component but if you only have one you can just do it like the example below.
Another good thing to do is changing your refs to functional ones since string refs are considered as legacy and will be removed in future releases of react. More info here.
class MyCMP extends Component {
constructor(props) {
super(props);
this.scroll = null;
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow.bind(this));
}
componentWillUnmount () {
this.keyboardDidShowListener.remove();
}
_keyboardDidShow() {
this.scroll.scrollToEnd();
}
render() {
return (
<View>
<ScrollView ref={(scroll) => {this.scroll = scroll;}}>
{ /* items */ }
</ScrollView>
<TextInput />
</View>
);
}
}
export default MyCMP;
If you have a large dataset React Native docs is telling you to go with FlatList.
To get it to scroll to bottom this is what worked for me
<FlatList
ref={ref => (this.scrollView = ref)}
onContentSizeChange={() => {
this.scrollView.scrollToEnd({ animated: true, index: -1 }, 200);
}}
/>

React Native Navigator: Can I remove navigation gestures from a scene after a user action?

I want to disable the swipe from left pop gesture on the navigator after the side menu has been accessed within a scene. I don't want to disable it when the scene first renders, only when the side menu is open. I have an onOpen function I can call, but I don't know how to programatically change the navigation gestures without pushing another route.
I tried setting the configureScene prop of the navigator like this:
configureScene={() => {
return this.state.swipeBackNavigation ? FloatFromRight : Navigator.SceneConfigs.FloatFromRight;
}
and changing the state, but the component doesn't rerender
ideas would be appreciated
I believe you can just set gestures to null (effectively disabling it):
gestures: {}
I can't test this currently, but I suspect it will work (if I didn't screw up some syntax somewhere):
export default class Foo extends Component {
constructor(props){
super(props);
this.state = {
//initialize gestureChoice
gestureChoice: {},
}
}
disablePop(){
setState({ gestureChoice:{ gestures:{} } });
}
enablePop(){
setState({gestureChoice: ...Navigator.SceneConfigs.FloatFromRight});
}
render(){
return(
<Navigator
renderScene={(route, navigator) =>
return <SomeScene navigator={navigator} {...route.passprops} />
}
configureScene={(route, routeStack) =>
this.state.gestureChoice;
)}
/>
);
}
}
The idea being, you could use enablePop() and disablePop() whenever you would like.
This thread is probably helpful: https://github.com/facebook/react-native/issues/1014

React Native passing functions with arguments as props

From what I have read its best to try and structure react apps with as many components as "dumb" renderers. You have your containers which fetch the data and pass it down to the components as props.
That works nicely until you want to pass functions down the chain that require arguments other than events.
class MyClass extends Component {
_onItemPress (myId) {
// do something using myId
}
render () {
return <MyComponent myID={10} onPress={(myId) => this._onItemPress(myId)} />
}
}
If I simply pass that as my onPress handler to MyComponent it won't return myId when called. To get around this I end up doing something like this.
export default ({myId, onPress) => {
const pressProxy = () => {
onPress(myId)
}
return (
<TouchableHighlight onPress={pressProxy}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
Am I doing this completely incorrectly? I would like to be able to have a simple component that I can re-use for list items where its sole function is to take a title, onpress function and return a list item to be used in ListViews renderRow function. Many of the onPress functions will require variables to be used in the onPress however.
Is there a better way?
The proper syntax would be something like this:
render () {
let myId = 10;
return <MyComponent myID={myId} onPress={() => this._onItemPress(myId)} />
}
Also, if you plan to use this inside _onItemPress (for example to call other methods in MyClass), you need to bind the scope like this:
render () {
let myId = 10;
return <MyComponent
myID={myId}
onPress={() => this._onItemPress(myId).bind(this)} />
}
...or you can bind it already in the constructor if you prefer:
constructor(props) {
super(props);
this._onItemPress = this._onItemPress.bind(this);
}
You did it correctly.
MyComponent is as "dumb" as it should be: it does not care about the source of its props, it acts independently from higher level of logic of the app and it can be reused somewhere else in the app.
Some improvements you can work on:
MyComponent does not need myId itself. Exclude it from the component and let parental component deals with related logics to id
Provide a safe check for props onPress. If you want to reuse MyComponent somewhere, it is better to check for existence of onPress property before calling it, or provide a default value for onPress in case developer adds in unwanted props types.
Example of MyComponent
class MyComponent extends Component {
handlePress = (e) => {
if (typeof this.props.onPress === 'function') {
this.props.onPress()
}
}
render() {
return (
<TouchableHighlight onPress={this.handlePress}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
}
and to call MyComponent in MyClass:
class MyClass extends Component {
_onItemPress(myId) {
}
render () {
return <MyComponent onPress={() => this._onItemPress(10)} />
}
}