Workaround expo react native web splashscreen - react-native

Is there any existing workaround to show a splashscreen on web? It is not yet supported, and I'd like to avoid seeing a white screen while loading the website.
Ref.: https://docs.expo.io/versions/v41.0.0/sdk/splash-screen/
Known issue on github: https://github.com/expo/expo/issues/10839

I tested (and use) it with SDK 47 and adapted the example on https://docs.expo.dev/versions/latest/sdk/splash-screen/#usage like this (I simplified some components and functions here for better readability, so this example never "run" like this in reality):
import React, { useEffect, useState } from 'react';
import { Text, View, Platform } from 'react-native';
import Entypo from '#expo/vector-icons/Entypo';
import * as SplashScreen from 'expo-splash-screen';
import * as Font from 'expo-font';
import { runAllTheInitStuff } from './init';
import SomeProvider from './SomeProvider';
import AnotherProvider from './AnotherProvider';
import WebSplashScreen from './WebSplashScreen';
// Keep the splash screen visible while we fetch resources
SplashScreen.preventAutoHideAsync();
export default function App() {
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
await runAllTheInitStuff();
// Tell the application to render
setAppIsReady(true);
// hide splash screen
await SplashScreen.hideAsync();
}
prepare();
}, []);
// check if app is ready
if(!appIsReady) {
// check if we are in web
if(Platform.OS === 'web') {
return <WebSplashScreen />;
} else {
return null;
}
}
return (
<SomeProvider>
<AnotherProvider>
<View
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>SplashScreen Demo! 👋</Text>
<Entypo name="rocket" size={30} />
</View>
</AnotherProvider>
</SomeProvider>
);
}
I do not use a <View> component as first entry point, but a lot of provider, so it would be quite challenging to use onLayout prop in my case. That's the reason why hiding the splash screen is done directly in the useEffect hook...
The WebSplashScreen component can be anything (i.e. the splash screen used in mobile app as image or what ever), I use a simple activity indicator from a material ui library...

Related

NativeBase: Button does not work, but ReactNative's Button does

Experiencing a strange issue in my React Native project for Android.
Using React-Navigation, I have a component with a button inside. This button should navigate to a new screen.
Thing is, the built-in button of React Native works like a charm, while the button of Native Base does not. I am completely confused, even more because I use this Native Base Button in another location, too. And there it works fine.
What is going on here?
Here, you see the application works with the built-in React Native button:
On the opposite, using the button of Native Base, it not only does not work, even styles are not applied.
Here is the code with the React Native button:
import React from "react";
import { Button, View, Text, StyleSheet } from "react-native";
import { withNavigation } from "react-navigation";
type Props = { navigation: any };
const ButtonTestScreen: React.FC<Props> = ({ navigation }) => {
return (
<View>
<Button
title="Hi i am a button"
onPress={() => navigation.navigate("Details")}
></Button>
</View>
);
};
export default withNavigation(ButtonTestScreen);
And the code with Native Base button:
import React from "react";
import { Button, View, Text, StyleSheet } from "react-native";
import { withNavigation } from "react-navigation";
import ButtonNavigate from "../../components/atoms/ButtonNavigate/ButtonNavigate";
type Props = { navigation: any };
const ButtonTestScreen: React.FC<Props> = ({ navigation }) => {
return (
<View>
<ButtonNavigate
title="Hi i am a button"
navigateTo="Details"
></ButtonNavigate>
</View>
);
};
const styles = StyleSheet.create({
button_style: {
backgroundColor: "red"
},
text_style: {
color: "#000",
fontSize: 30
}
});
export default withNavigation(ButtonTestScreen);
And the respective ButtonNavigate component itself:
import React from "react";
import { StyleSheet } from "react-native";
import { withNavigation } from "react-navigation";
import { Button, Text } from "native-base";
type Props = {
title: string,
navigateTo: string,
navigation: any
};
const ButtonNavigate: React.FC<Props> = ({ title, navigateTo, navigation }) => {
return (
<Button
rounded
transparent
style={styles.button_style}
onPress={() => navigation.navigate(navigateTo)}
>
<Text style={styles.text_style}>{title}</Text>
</Button>
);
};
const styles = StyleSheet.create({
button_style: {
backgroundColor: "red"
},
text_style: {
color: "#151414"
}
});
export default withNavigation(ButtonNavigate);
I have just tested you code in expo.snack but without navigation and its ok,
see it here
You can test in your app to remove navigation and go step by step until you find the bug.
Folks, reason for this strange behavior is the "rounded" property of Native Base's button. In my application, somehow it causes the button to become non-clickable.
Maybe contributors of Native Base know what to do with this problem, so if you read this, maybe you have an idea.
Solution for my now was simply removing "rounded".
Native Base: 2.13.8
React-Navigation: 4.0.10
In my case it was the "top" in the container property of the button causing this issue. Removed it and adding "marginBottom" to the container above it solved the issue

React Navigation - How to pass data across different screens in TabNavigator?

I have a TabNavigator, and in each tab is a StackNavigator. Inside the StackNavigator, I have screens. The screens in each Tab do not call each other directly; the TabNavigator handles the screen changes when a tab is pressed.
In the first tab, if the user clicks a button, some data is created. If the user then navigates to the second Tab, I would like to pass this data to the screen in the second Tab.
Here is a demo of the code:
import React from 'react';
import { Button, Text, View } from 'react-native';
import {
createBottomTabNavigator,
createStackNavigator,
} from 'react-navigation';
class HomeScreen extends React.Component {
doIt = () => {
this.props.navigation.setParams({results: ['one', 'two']}); // <--- set data when user clicks button.
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{/* other code from before here */}
<Button
title="Set Results"
onPress={this.doIt}
/>
</View>
);
}
}
class SettingsScreen extends React.Component {
render() {
console.log(this.props.navigation); // <--- console out when user clicks on this tab
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings</Text>
</View>
);
}
}
const HomeStack = createStackNavigator({
Home: HomeScreen,
});
const SettingsStack = createStackNavigator({
Settings: SettingsScreen,
});
export default createBottomTabNavigator(
{
Home: HomeStack,
Settings: SettingsStack,
},
{
}
);
The this.props.navigation.state.params never gets the data results in the second Tab. There isn't even a key for it, so if I try to access this.props.navigation.state.params.results, it will be undefined.
This is confusing because I thought props.navigation is passed to all screens automatically.
How can I pass data from one screen to another through the TabNavigator, using just react-navigation? I have seen answers that say to use Redux, but I would not like to import another library if all I want is to keep some state across screens in different react navigators.
It may seem that this.props.navigation.state.params is only able to old one parameter? Possibly? Try this:
doIt = () => {
this.props.navigation.setParams({results: 'one'}); // <--- set data when user clicks button.
}
console.log(this.props.navigation.state.params.results);
Setting props did not work when passing data across different tabs. I even tried playing with AsyncStorage, trying to save and retrieve them in different tabs.
I ended up using Redux to save my states, and that has worked well so far.
I came across a similar problem. I had a multi page form that the client insisted on having each step be enclosed in a tab on a tab bar. I used the react navigation createMaterialTopTabNavigator to create the navigator and couldn't find an easy way to pass the form data between tabs.
What I end up doing was using react's Context API and wrapped the tab navigator in a root form container that provides the context value to the navigator and routes inside. Here is how I did it:
Root form container
// MultiScreenForm.js
imports...
import MultiScreenFormNavigator from './MultiScreenFormNavigator'
export const FormContext = React.createContext()
class MultiScreenForm extends Component {
constructor(props) {
super(props)
this.state = {
// formDataHere
formUpdaters: {
onToggleOptIn: this.handleToggleOptIn // example updater method
// other
}
}
}
handleToggleOptIn = () => {
// toggle opt in form data with this.setState
}
render() {
return (
<FormContext.Provider value={this.state}>
<MultiScreenFormNavigator />
</FormContext.Provider>
)
}
}
export default MultiScreenForm
Example form page
// ProfileForm.js
imports...
import { FormContext } from './MultiScreenForm'
class ProfileForm extends Component {
render() {
// FormContext.Consumer uses function as child pattern
return (
<FormContext.Consumer>
{ (context) => (
// our form can now use anything that we pass through the context
// earlier, we passed the root form's state including an updater
<button onPress={context.formUpdaters.onToggleOptIn} />
// ...
)
}
</FormContext.Consumer>
)
}
}
export default ProfileForm
Tab navigator
// MultiScreenFormNavigator.js
imports...
import ProfileForm from './ProfileForm'
import { createMaterialTopTabNavigator } from 'react-navigation'
const MultiScreenFormNavigator = createMaterialTopTabNavigator(
{
Profile: ProfileForm,
// AnotherForm: AnotherForm
},
// { navigator options here... }
)
export default MultiScreenFormNavigator
We then render the MultiScreenForm instead of the tab navigator directly.
This worked for me but I feel there should be an easier way to do this. I hope people who read this can share their approaches.
#tempomax
tried same with AsyncStorage but data came in with a delay.
Sometimes you don't need Redux if your app stays small.
So tried to find a way without Redux.
Here is what I came up with
I hope it's not too late to answer.
Solved it with NavigationEvents and setting params to Route.
The problem with tab is that you can´t pass params to screen because navigation.navigate will be triggered automatically if createMaterialTopTabNavigator is swiped or clicked on non-active TabBar Button.
This can be solved with NavigationEvent like follow.
import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
const MyScreen = () => (
<View>
<NavigationEvents
onWillFocus={payload => console.log('will focus',payload)}
onDidFocus={payload => console.log('did focus',payload)}
onWillBlur={payload =>
/*
if screen is about to change this will be triggred
In screen 'MyScreen2' you can get it with navigation.params
*/
this.props.navigation.navigate('MyScreen2', { name: 'Brent' })
}
onDidBlur={payload => console.log('did blur',payload)}
/>
{/*
Your view code
*/}
</View>
);
export default MyScreen;
Now you can get the data in MyScreen2
/* 2. Get the param, provide a fallback value if not available */
const { navigation } = this.props;
const itemId = navigation.getParam('name', 'DefaultName');
const otherParam = navigation.getParam('otherParam', 'some default value');
If you are using React Native Navigation Version 5.x with a DrawerNavigation, you can do this using
in screen 1:
<Button
onPress={() => {
this.props.navigation.navigate(<ScreenNameOfDrawerScreen>,
{screen:'<ScreenNameInTabDrawer>',params:{your_json_Data}});
}} />
in screen 2:
............
render() {
if(this.props.route.params!=undefined){
if(this.props.route.params.your_json_Data!=null){
// Use this.props.route.params.your_json_Data. It is your json data.
}
}
return (
..............

react native touchable highlight and touchable native feedback for making my menu items

I am trying to implement a basic drawer, but I am confused by the documentation.
There is a TouchableHighlight, and there is a TouchableNativeFeedback.
TouchableNativeFeedback is Android only. So, how can I best handle my MenuItem component so it takes care of both Android and IOS?
Here's what I've got so far, and I'm not sure how to handle the different platform specific touchable stuff here:
import React, { Component, PropTypes } from 'react';
import { TouchableHighlight, TouchableNativeFeedback, Platform, View, Text } from 'react-native';
export class MenuItem extends Component {
handleAndroid() {
}
handleIOS() {
}
renderMenuItem() {
return (
<View style={styles.container}>
<Text style={styles.text}>
{this.props.text}
</Text>
</View>
);
}
render() {
return (
);
}
}
On Android, do I have to use TouchableNativeFeedback only?
Like you said, TouchableNativeFeedback is Android only therefore it won't work for iOS. If you want a solution that works for both, use TouchableHighlight and style it accordingly. For example, its underlayColor prop allows you to set "the color of the underlay that will show through when the touch is active."
EDIT:
If you want to use TouchableNativeFeedback for Android and TouchableHighlight for iOS, you should do something like the following:
import { Platform } from 'react-native'
...
render() {
if (Platform.OS === 'android') {
return (
<TouchableNativeFeedback>
...
</TouchableNativeFeedback>
)
} else {
return (
<TouchableHighlight>
...
</TouchableHighlight>
)
}
}
EDIT 2:
Alternatively, you can create a custom component for each platform and use file extensions. For example:
MyButton.ios.js
MyButton.android.js
Put these two in the same folder and then use MyButton as a regular component:
import MyButton from '../path/to/component/MyButton'
...
render() {
<MyButton/>
}
This is quite neat when you want to use this component in multiple places because you don't fill your code with if-else blocks.
Here you can read more about Platform Specific Code.
You can solve this in a more elegant manner like this:
render() {
let TouchablePlatformSpecific = Platform.OS === 'ios' ?
TouchableOpacity :
TouchableNativeFeedback;
let touchableStyle = Platform.OS === 'ios' ?
styles.iosTouchable :
styles.androidTouchable
return(
<TouchablePlatformSpecific style={touchableStyle}>
(...)
</TouchablePlatformSpecific>
);
}
I've been using this successfully, and I fail to see a reason it would not work, it looks good to me.
A better implementation would be to have dumb component that produces generic Touchable that works depending upon the platform and handle onPress events.
Touchable Component:
import { Platform } from "react-native"
import { TouchableNativeFeedback, TouchableOpacity } from "react-native"
import React from "react"
export const Touchable = (props: any) => {
return Platform.OS === 'android'
? <TouchableNativeFeedback onPress={props.onPress}>{props.children}</TouchableNativeFeedback>
: <TouchableOpacity onPress={props.onPress}>{props.children}</TouchableOpacity>
}
Usage:
import { Touchable } from '../path/to/touchable.component';
...
render() {
<Touchable onPress={() => this.handleClick()}>
.....
</Touchable>
}
Thanks to #Giorgos' implementation which formed the basis for this answer.
Or you could create a custom component like this:
import { TouchableNativeFeedback, TouchableOpacity } from 'react-native'
...
const Touchable = (props) => {
return Platform.OS === 'android'
? <TouchableNativeFeedback>{props.children}</TouchableNativeFeedback>
: <TouchableOpacity>{props.children}</TouchableOpacity>
}
And then use it like this:
<Touchable>
<Card>
<Text>Click me!</Text>
</Card>
</Touchable>
I also have nearly the same requirements and I ended up using this library react-native-haptic-feedback.
Keep in mind that haptic feedback is available only on some latest android devices and in iOS above iPhone 6s. For devices without haptic motor, there will be vibration. Here is a sample code snippet:
import ReactNativeHapticFeedback from "react-native-haptic-feedback";
const options = {
enableVibrateFallback: true,
ignoreAndroidSystemSettings: false
};
ReactNativeHapticFeedback.trigger("impactMedium", options);
In your case, it will work directly with the button's onPress method such as:
<TouchableOpacity onPress={()=>{ReactNativeHapticFeedback.trigger("impactMedium", options);}} />
Note: I found that the most versatile type which is supported by maximum devices is impactMedium.
Based on #Nithish JV and Giorgos Karyofyllis:
import React from "react"
import { Platform } from "react-native"
import { TouchableNativeFeedback, TouchableOpacity } from "react-native"
export const TouchableFeedback = (props: any) => {
const { children, ...rest } = props;
return Platform.OS === 'android'
? <TouchableNativeFeedback {...rest}>{children}</TouchableNativeFeedback>
: <TouchableOpacity {...rest}>{children}</TouchableOpacity>
}
The best elegant way to do it
import { TouchableNativeFeedback, TouchableOpacity } from 'react-native'
in render:
TouchableButton = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity
return (
<TouchableButton>{'bla bla'}</TouchableButton>
)
Since the operating system doesn't change magically during runtime, I always use this little snippet:
import { Component } from "react"
import {
Platform,
TouchableNativeFeedback,
TouchableOpacity,
} from "react-native"
// The operating system doesn't magically change,
// so we can call the function directly to get the correct component
export default ((): typeof Component => {
if (Platform.OS === "android") {
return TouchableNativeFeedback
}
return TouchableOpacity // Choose whatever touchable you like
})()
It's basically an IIFE that selects the correct touchable for the operating system.

React Native Android lag during animation

I experience the lag during transition animation when Navigator goes to below scene ShiftEdit. Animation starts immediately but it stops for a millisecond. InteractionManager is used to postpone rendering of four picker components. Every picker component has list of items that is built from an array. There is lots of items. Is it possible that this is calculated even when picker component isn't rendered yet in ShiftEdit and this is the reason of the lag? Could you help me please?
'use strict'
import React, {View, Text, StyleSheet, InteractionManager, TouchableOpacity} from 'react-native';
import { connect } from 'react-redux';
import Spinner from 'react-native-spinkit';
import StyleCommon from '../styles';
import TimePicker from '../components/time-picker';
import ColorPicker from '../components/color-picker';
import LabelPicker from '../components/label-picker';
class ShiftEdit extends React.Component {
constructor(props) {
super(props);
this.state = {
isReady: false,
shiftId: '',
startHour: '',
endHour: '',
color: '',
}
}
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this.setState({isReady: true});
});
}
onChangeItem = (label, val) => {
let data = {};
data[label] = val;
this.setState(data);
}
renderPlaceholder() {
return (
<View style={styles.container}>
<Text>Loading...</Text>
</View>
)
}
render() {
if (!this.state.isReady) {
return this.renderPlaceholder();
}
return (
<View style={{flex:1, flexDirection: 'column'}}>
<TimePicker label={'Start hour'} value={this.state.startHour} onChange={this.onChangeItem.bind(this, 'startHour')} />
<TimePicker label={'End hour'} value={this.state.endHour} onChange={this.onChangeItem.bind(this, 'endHour')} />
<ColorPicker label={'Color'} value={this.state.color} onChange={this.onChangeItem.bind(this, 'color')} />
<LabelPicker label={'Shift ID'} value={this.state.shiftId} onChange={this.onChangeItem.bind(this, 'shiftId')} />
</View>
)
}
};
I tried to control animation registration as Chris suggested but it still the same:
onPress = () => {
let handle = InteractionManager.createInteractionHandle();
this.props.navigator.push({component: 'shiftedit'});
InteractionManager.clearInteractionHandle(handle);
}
Actually this is the only solution that works for me now:
componentDidMount() {
// InteractionManager.runAfterInteractions(() => {
// this.setState({isReady: true});
// })
setTimeout(() => {
this.setState({isReady: true});
}, 75);
}
but I'd rather use InteractionManager...
Here's a wild guess as I've no experience with InteractionManager directly. But after looking over the Interaction Manager Docs I noticed that there's a way to register animations. So, my guess is that the Navigator's animations haven't been properly registered. So maybe try something like this...
var handle = InteractionManager.createInteractionHandle();
// run your navigator.push() here... (`runAfterInteractions` tasks are queued)
// later, on animation completion:
InteractionManager.clearInteractionHandle(handle);
// queued tasks run if all handles were cleared
Hope that helps!
Also, keep in mind that if you're running the React Native Debugger while testing your app, React Native animations will appear jittery on Android.
That's been my experience.
https://github.com/jhen0409/react-native-debugger

how could I swipe the page from left to right in order to go back in a webview?

In a web view,I want to swipe the page from left to right in order to go back,just like what safari did.
what should I do?
TL/DR: Currently, you'll need a third party package for that. Use the react-native-wkwebview-reborn package and set the allowsBackForwardNavigationGestures prop to true. Example:
WebView.ios.js:
import React from 'react';
import WKWebView from 'react-native-wkwebview-reborn';
export default (props) => <WKWebView allowsBackForwardNavigationGestures {...props} />
WebView.js
import { WebView } from 'react-native';
export default WebView;
It's a drop-in replacement, so you won't need to change much code.
Why:
The WebView component from React Native uses UIWebView under the hood, which is not recommended by Apple anymore:
It has worse performance and does not support a lot of features, like 3D Touch and swipe back gesture.
Join this discussion so react native updates their core component.
You can use react-native-webview package. And just pass allowsBackForwardNavigationGestures prop to WebView:
import WebView from 'react-native-webview';
<WebView
allowsBackForwardNavigationGestures
...
/>
I know it's been a long time but I came across with the same problem and couldn't find any working solutions for android. So I came up with mine. Also, this post is first to show up when you google 'react native Webview swipe back', so I thought it would help more people posting my solution here.
import * as React from "react";
import { WebView } from "react-native-webview";
import { StyleSheet, Platform, BackHandler } from "react-native";
export default function App() {
const webViewRef = React.useRef();
const onAndroidBackpress = () => {
if (webViewRef.current) {
webViewRef.current.goBack();
return true;
}
return false;
};
return (
<WebView
onTouchStart={e => {
if(Platform.OS === 'android'){
this.touchX = e.nativeEvent.pageX;
this.touchY = e.nativeEvent.pageY
} else {
return null
}}
}
onTouchEnd={e => {
if(Platform.OS === 'android' && this.touchX - e.nativeEvent.pageX < -20){
if(this.touchY - e.nativeEvent.pageY > -20 && this.touchY - e.nativeEvent.pageY < 20){
onAndroidBackpress()
}
} else {
return null
}}
}
allowsBackForwardNavigationGestures // only works with iOS
allowsInlineMediaPlayback
ref={webViewRef}
style={styles.container}
source={{ uri: "https://<yourwebsite>.com" }}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});