Sorry for the dummy question, but I'm really stuck.
I created a very simple react-native app using these instructions.
Then I changed App.js to be
import React from 'react';
import {AppRegistry, Button, View } from 'react-native';
class RootView extends React.Component {
state = {
showFoo: false,
}
showFoo = () => {
this.setState({showFoo: true})
}
renderFoo = () => {
if (this.state.showFoo) {
console.log("at 4");
const item = <View />; /// BOOM!
console.log('at 5', item);
return item;
}
return null;
}
render = () => {
const renderFoo = this.renderFoo();
return (
<View>
<Button title="Press Me" onPress={this.showFoo} />
</View>
);
}
}
export default RootView;
Now, if I launch it using the Expo client on my Android 6.0.1. and I press "Press Me" button, it hangs and doesn't respond to back button.
In adb logcat, I can see "at 4", but never "at 5". It is like it stucks at "BOOM!" line for some reason (a dead lock?).
Wondering if I'm doing something wrong? My apologies again, but I have already spent a fair amount of time on this, would really appreciate any clue. Also, how could I debug things like that one?
Versions of react dependencies:
"dependencies": {
"expo": "^27.0.1",
"react": "16.3.1",
"react-native": "~0.55.2"
}
}
(Please let me know if you feel the question needs more details in the comments, I'm happy to update it.)
You should either use
<Button title="Press Me" onPress={this.showFoo.bind(this)}/>
or use
<Button title="Press Me" onPres{()=>this.showFoo()}/>
and also call this.renderFoo(); inside render method, with this syntax
{this.renderFoo()}
I've created a snack to show you the solution.
The problem is with your render method
render = () => {
const renderFoo = this.renderFoo();
return (
<View>
<Button title="Press Me" onPress={this.showFoo} />
</View>
);
}
Specifically const renderFoo = this.renderFoo();. For this line of code, you only execute the renderFoo method and store it into renderFoo variable, and it's not within the return portion. In other words the <View /> returned by renderFoo will not show on the screen.
The fix is by the below code
render() {
return (
<View>
<Button title="Press Me" onPress={this.showFoo} />
{this.renderFoo()}
</View>
);
}
Finally, after hours of investigation...
As it appears, the problem was this line:
console.log('at 5', item);
It hasn't actually stuck. If I waited for 5+ seconds, the app proceeded. Seems like logging a react component is a very expensive operation.
Same problem I had when I was trying to log some lambda function.
TLDR: don't log complex objects!!!!
React native for android run very slow when not enable "Debug JS"
https://facebook.github.io/react-native/docs/performance.html#using-consolelog-statements
Related
I'm coding an onboarding process that depends on a state. For each "page" the user completes the state increments and returns the next JSX page. At the last step, I'm navigating the user to a page called explore page.
Below is the bug I'm getting when I don't have the setTimeout method around the navigation.navigate function. The full error trace leads back to the navigate function.
This is a snippet of the error:
index.js:1 Warning: Cannot update a component (ForwardRef(BaseNavigationContainer)) while rendering a different component (OnboardingTemplate). To locate the bad setState() call inside OnboardingTemplate, follow the stack trace as described in https://facebook.me/setstate-in-render
Each step works fine and it returns the JSX but it seems like that because the view hasn't finished rendering it throws the above error while loading the explore page. The error occurs even though the explore page gets shown. I've tried with useEffect and everything I could think of but I can't figure it out. And it seems weird if setTimeout is the right solution.
Here is the code:
import React, {useEffect, useState} from 'react'
import {View,ScrollView,StyleSheet} from 'react-native'
import Button from '../ui/atoms/Button'
import { useNavigation } from '#react-navigation/native'
let currentStep : JSX.Element;
export default function OnboardingTemplate(){
const navigation : any = useNavigation()
const [loaded,setLoaded] = useState(false);
useEffect(() => {
setLoaded(true)
})
const [onboardingStep, setOnboardingStep]:any = useState(0);
if (onboardingStep == 0){
currentStep=(
<ScrollView>
<View style={styles.topDistance}>
<Button onPress={() => setOnboardingStep(1)}
style={styles.inputBtn} title="Fortsæt (1/2)"/>
</View>
</ScrollView>
)
} else if (onboardingStep == 1){
currentStep=(
<ScrollView>
<View style={styles.topDistanceSmall}>
<Button onPress={() => setOnboardingStep(2)}
style={styles.inputBtn} title="Opret profil"/>
</View>
</ScrollView>
)
} else if (loaded) {
navigation.navigate("explore");
}
return (currentStep) ? currentStep : null ;
}
I implemented a custom react-native TextInput backed by a native library. It's working pretty well except that when I tap outside of the textfield, it doesn't blur automatically and the keyboard doesn't disappear. I also tried with Keyboard.dismiss(), it doesn't work either. I looked at the 'official' TextInput implementation to replicate it without any success.
I added this code in my custom implementation (componentDidMount)
if (this.context.focusEmitter) {
this._focusSubscription = this.context.focusEmitter.addListener(
'focus',
el => {
if (this === el) {
this.requestAnimationFrame(this.focus);
} else if (this.isFocused()) {
this.blur();
}
},
);
if (this.props.autoFocus) {
this.context.onFocusRequested(this);
}
} else {
if (this.props.autoFocus) {
this.requestAnimationFrame(this.focus);
}
}
and I also defined the required contextTypes
static contextTypes = {
focusEmitter: PropTypes.instanceOf(EventEmitter)
}
code from TextInput
The problem I have is that the focusEmitter is undefined in the context and I have no idea from where it's provided in the context nor if it's actually the way it works for the regular TextInput. The only occurence of focusEmitter I could find in the react-native repo is in NavigatorIOS which I don't even use in my app.
Could anyone clarify this to me?
The simpler way to do what you want is to use Keyboard.dismiss() on a TouchableWithoutFeedback just like following example:
import {Keyboard} from 'react-native';
...
render(){
return(
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<View>
// Return everything here
<TextInput />
</View>
</TouchableWithoutFeedback>
)
}
So when you tap outside the input it will dismiss keyboard and blur the TextInput.
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);
}}
/>
I'd like to have a context menu triggered on long press different places using React Native.
I.e. in a dialer like the default dailer. You can long-click on any contact and get a 'copy number' menu. And also you can long-click on the name of the person once you've opened their 'contact card'.
The straight-forward way needs a lot of copy-pasted boilerplate, both components and handlers.
Is there a better pattern for doing this?
All Touchable components (TouchableWithoutFeedback, TouchableOpacity etc.) has a property called onLongPress. You can use this prop to listen for long presses and then show the context menu.
To eliminate code mess and doing lots of copy paste you can separate your context menu as a different component and call it when the long press happen. You can also use an ActionSheet library to show the desired options. React native has a native API for iOS called ActionSheetIOS. If you get a little bit more experience in react and react-native you can create a better logic for this but I'm going to try to give you an example below.
// file/that/contains/globally/used/functions.js
const openContextMenu = (event, user, callback) => {
ActionSheetIOS.showActionSheetWithOptions({
options: ['Copy Username', 'Call User', 'Add to favorites', 'Cancel'],
cancelButtonIndex: [3],
title: 'Hey',
message : 'What do you want to do now?'
}, (buttonIndexThatSelected) => {
// Do something with result
if(callback && typeof callback === 'function') callback();
});
};
export openContextMenu;
import { openContextMenu } from './file/that/contains/globally/used/functions';
export default class UserCard extends React.Component {
render() {
const { userObject } = this.props;
return(
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done')}>
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done'))}>
<Text>{userObject.name}</Text>
<Image source={{uri: userObject.profilePic }} />
</TouchableWithoutFeedback>
</TouchableWithoutFeedback>
);
}
}
Similarly as the previous answer combine onLongPress with imperative control for popup menu - something like
<TouchableWithoutFeedback onLongPress={()=>this.menu.open()}>
<View style={styles.card}>
<Text>My first contact name</Text>
<Menu ref={c => (this.menu = c)}>
<MenuTrigger text="..." />
<MenuOptions>
// ...
</MenuOptions>
</Menu>
</View>
</TouchableWithoutFeedback>
When it comes to a lot of boilerplate - in React you can do your own components that you can reuse everywhere thus reducing boilerplate (and copy&paste)
See full example on https://snack.expo.io/rJ5LBM-TZ
Using navigator I hit home from my react native android app and then return to the app and it always starts at the initial route which is my splash screen. Is there away to keep the component that was in view last the component that will be in view when the app re-opens?
class AwesomeProject extends Component {
render() {
return (
<Navigator
style={{ flex:1 }}
initialRoute={{ id: 'SplashPage' }}
renderScene={ this.renderScene }
/>
);
}
renderScene(route, navigator) {
if (route.id === 'SplashPage') {
return (
<SplashPage
navigator={navigator} {...route.passProps}
/>
);
}else if(route.id === 'HomePage'){
return (
<HomePage
navigator={navigator} {...route.passProps}
/>
);
}else if(route.id === 'ListViewPage'){
return (
<ListViewPage
navigator={navigator} {...route.passProps}
/>
);
}
}
}
You need to add the following code to /android/app/src/main/java/com//MainActivity.java. so, that it maintains the stacks of application.
#Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
if you dont achieve your answer from above please follow reference as below:
https://facebook.github.io/react-native/docs/native-modules-android.html
and in last of above reference there is lifecycle event for application in react-native. So, follow same strategy like native android onPause/onDestroy/onStop method and please solve your problem.
can you make sure you app is live when you press home?
My first suggestion is not to use splashscreen as a scene. If you use that in such a way it is not splashscreen rather a component. Use the native java code as mentioned in the links below to get your problem solved like charm.
https://github.com/react-native-component/react-native-smart-splash-screen
with this there are certain import errors which can be fixed looking at the issues section or check my code in github which contains the perfect use of the splashscreen module.
https://github.com/UjjwalNepal/Dental
Hope this helps.
Thank you