React Native RefreshControl not triggering onRefresh on iOS - ios

I've got the following (simplified) setup for my PersonScreen, which navigate to AppointmentScreen:
class PersonScreen {
state = {
refreshing: false,
};
_onRefresh = () => {
this.setState({ refreshing: true });
this._fetchStuff()
.then(() => {
this.setState({ refreshing: false });
};
};
render() {
return (
<View style={CONTAINER}>
<ScrollView
keyboardShouldPersistTaps="handled"
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={this._onRefresh} />}
<ListItem
key={ITEM.id}
title={moment(DATETIME).format(FORMAT))}
onPress={() => {
navigation.navigate('AppointmentScreen', {
appointment: ITEM,
refreshParent: this._onRefresh,
});
}
/>
</ScrollView>
</View>
);
}
}
On the AppointmentScreen you make some choices and then to go back the following is performed:
const { refreshParent } = navigation.state.params;
refreshParent();
navigation.goBack();
The only scenario where this does not work is on iOS. It works as long as I go to PersonScreen and refresh. However, if I go from PersonScreen to AppointmentScreen and back it does trigger the refreshParent (which is essentially _onRefresh), but then any attempts to pull down to trigger the RefreshControls onRefresh function fails. I've also added some console.log in the _onRefresh, but it doesn't even output anything.
If my AppointmentScreen instead looks like this:
const { refreshParent } = navigation.state.params;
//refreshParent();
navigation.goBack();
Everything works. So I'm experiencing that somehow calling refreshParent and then going back makes the following _onRefresh from the RefreshControl in PersonScreen not work.
Does anyone have any ideas why this is the case? As mentioned, I only experience this on iOS, but consistently so on iOS simulator and devices.

Can you create an expo version?
also, your refreshing is refreshing={refreshing} it should be refreshing={this.state.refreshing}

Related

Conditional header in react-native StackNavigator

I'm trying to control whether or not my StackNavigator header appears via this.props.navigation.state.params.
I have a screen with the following navigationOptions:
static navigationOptions = ( {navigation} ) => ({
header: navigation.state.params.headerConfig,
});
and I navigate to the screen as follows:
<Button
onPress={() => navigate('MyScreen', { headerConfig: _____} ) }
title="Continue"
/>
, where ____ is what I am unsure about. If I put null then the header disappears, but what can I put there if I don't want the header to disappear?
I tried entering HeaderProps instead of ____.
Any help or alternative approaches would be much appreciated.
If you don't want it do disappear then don't set it to null, leave it undefined and you will get the default one.
static navigationOptions = ({navigation}) => {
if (navigation.state.params.hideHeader) {
return {header: null}
}
return {title: 'Home'}
}

Open Webview on TouchableOpacity onPress React-Native

Presently I am using Linking but it is opening the url outside the app, I want this app to be opened within the app without showing the URL.
But unable to open Webview on TouchableOpecity onPress event in React-Native. Do I need to add a page and then open the page with URL ?
Can anyone please help.
I am considering the simplest of cases where i am rendering a single component and no navigator is being used.
class ABC extends Component {
constructor(props){
super(props)
this.state = {
check : false
}
}
renderWebView(){
if(this.state.check){
return(
<WebView
source={{uri: 'your url goes here'}}
style={{marginTop: 20}}
/>
);
}else {
return(
<TouchableOpacity
onPress={()=>this.setState({check: true})}>
<Text>Open WebView</Text>
</TouchableOpacity>
);
}
}
render() {
return (
<View style={{flex:1}}>
{this.renderWebView()}
</View>
);
}
}
You can use one of the Navigators and treat the webview component as one route.

React Bootstrap OverlayTrigger with trigger="focus" bug work around

In iOS safari, OverlayTrigger with trigger="focus" isn't able to dismiss when tapping outside. Here is my code:
<OverlayTrigger
trigger="focus"
placement="right"
overlay={ <Popover id="popoverID" title="Popover Title">
What a popover...
</Popover> } >
<a bsStyle="default" className="btn btn-default btn-circle" role="Button" tabIndex={18}>
<div className="btn-circle-text">?</div>
</a>
</OverlayTrigger>
I know that this is a known bug for Bootstrap cuz this doesn't even work on their own website in iOS, but does anyone know any method to go around it? It would be the best if it is something that doesn't require jQuery, but jQuery solution is welcome. Thanks.
OK, since no one else gives me a work around, I worked on this problem with my co-worker together for 3 days, and we came up with this heavy solution:
THE PROBLEM:
With trigger="focus", Bootstrap Popover/Tooltip can be dismissed when CLICKING outside the Popover/Tooltip, but not TOUCHING. Android browsers apparently changes touches to clicks automatically, so things are fine on Android. But iOS safari and browsers that is based on iOS safari (iOS chrome, iOS firefox, etc...) don't do that.
THE FIX:
We found out that in React Bootstrap, the Overlay component actually lets you customize when to show the Popover/Tooltip, so we built this component InfoOverlay based on Overlay. And to handle clicking outside the component, we need to add event listeners for both the Popover/Tooltip and window to handle both 'mousedown' and 'touchstart'. Also, this method would make the Popover have its smallest width all the time because of the padding-right of the component is initially 0px, and we make based on the width of some parent component so that it is responsive based on the parent component. And the code looks like this:
import React, { Component, PropTypes as PT } from 'react';
import {Popover, Overlay} from 'react-bootstrap';
export default class InfoOverlay extends Component {
static propTypes = {
PopoverId: PT.string,
PopoverTitle: PT.string,
PopoverContent: PT.node,
// You need to add this prop and pass it some numbers
// if you need to customize the arrowOffsetTop, it's sketchy...
arrowOffsetTop: PT.number,
// This is to be able to select the parent component
componentId: PT.string
}
constructor(props) {
super(props);
this.state = {
showPopover: false,
popoverClicked: false
};
}
componentDidMount() {
// Here are the event listeners and an algorithm
// so that clicking popover would not dismiss itself
const popover = document.getElementById('popoverTrigger');
if (popover) {
popover.addEventListener('mousedown', () => {
this.setState({
popoverClicked: true
});
});
popover.addEventListener('touchstart', () => {
this.setState({
popoverClicked: true
});
});
}
window.addEventListener('mousedown', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
window.addEventListener('touchstart', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
// this is to resize padding-right when window resizes
window.onresize = ()=>{
this.setState({});
};
}
// This function sets the style and more importantly, padding-right
getStyle() {
if (document.getElementById(this.props.componentId) && document.getElementById('popoverTrigger')) {
const offsetRight = document.getElementById(this.props.componentId).offsetWidth - document.getElementById('popoverTrigger').offsetLeft - 15;
return (
{display: 'inline-block', position: 'absolute', 'paddingRight': offsetRight + 'px'}
);
}
return (
{display: 'inline-block', position: 'absolute'}
);
}
overlayOnClick() {
this.setState({
showPopover: !(this.state.showPopover)
});
}
render() {
const customPopover = (props) => {
return (
{/* The reason why Popover is wrapped by another
invisible Popover is so that we can customize
the arrowOffsetTop, it's sketchy... */}
<div id="customPopover">
<Popover style={{'visibility': 'hidden', 'width': '100%'}}>
<Popover {...props} arrowOffsetTop={props.arrowOffsetTop + 30} id={this.props.PopoverId} title={this.props.PopoverTitle} style={{'marginLeft': '25px', 'marginTop': '-25px', 'visibility': 'visible'}}>
{this.props.PopoverContent}
</Popover>
</Popover>
</div>
);
};
return (
<div id="popoverTrigger" style={this.getStyle()}>
<a bsStyle="default" className="btn btn-default btn-circle" onClick={this.overlayOnClick.bind(this)} role="Button" tabIndex={13}>
<div id="info-button" className="btn-circle-text">?</div>
</a>
<Overlay
show={this.state.showPopover}
placement="right"
onHide={()=>{this.setState({showPopover: false});}}
container={this}>
{customPopover(this.props)}
</Overlay>
</div>
);
}
}
In the end, this is a heavy work around because it is a big amount of code for a fix, and you can probably feel your site is slowed down by a tiny bit because of the 4 event listeners. And the best solution is just tell Bootstrap to fix this problem...

Inject JavaScript or CSS into React Native WebView before page renders

I want to hide a header of a specific page that I embed into a React Native WebView. Currently, I use something like this, to dynamically remove the header:
<WebView
... some other props ...
injectedJavaScript={'function hideHead(){document.getElementById("head").style.display="none";};hideHead();'}
/>
Sometimes you can still see the header flashing, so I guess this JavaScript gets evaluated after the page loads in the WebView.
Is it possible somehow to add / inject JavaScript or CSS before the page renders to remove that flashing?
I couldn't find a way to inject JavaScript or CSS before the page loads.
To workaround the flashing problem, I put another View on top of the WebView while it is still in loading state. The onNavigationStateChanged callback can be used to find out whether the page is loaded. The View I put on top simply shows an ActivityIndicatorIOS.
I faced this issue, my solution was like that
render() {
const remove_header = 'header = document.querySelector("header"); header.parentNode.removeChild(header);';
uri = this.props.url
return (
<View style={{flex:1 }}>
{(!this.state.showWebView) && <ActivityIndicator size="large" /> }
<WebView
style={this.state.showWebView ? {flex:1} : {flex: 0} }
source={{uri: uri}}
javaScriptEnabled
injectedJavaScript={'function injectRules() {'remove_header'};injectRules();'}
onNavigationStateChange={(event) => {
if (event.url !== uri) {
this.webview.stopLoading()
Linking.openURL(event.url)
}
}}
onLoadStart={() => {
this.setState({ showWebView: false })
}}
onLoadEnd={() => {
if (!this.state.showWebView ) {
this.setState({ showWebView: true })
}
}}
/>
</View>
)
}
My quick solution was to add a negative marginTop to the WebView to hide the header. By doing this, the header is hidden before the render.
webView: {
...
marginTop: -80,
},

react native render only part

I have a feed and a bottom menu which can filter the feed.
When the filter is activated the feed reloads data from the API.
However the root will always render causing the menu to collapse every time.
How can I make it so that the Feed only reloads?
var Main = React.createClass({
getInitialState: function() {
return {
reloadFeedData: false,
};
},
reloadFeedData: function(){
console.log('setting root state')
this.setState({reloadFeedData:true});
},
renderScene: function(route, nav) {
var reload = this.state.reloadFeedData
switch (route.name) {
case 'Feed':
return (
<Feed navigator={nav} reloadFeedData={reload} />
);
default:
return (
<Feed navigator={nav} reloadFeedData={reload} />
);
}
},
render: function() {
console.log('root render');
return (
<View style={styles.container}>
<Navigator
style={styles.navigator}
renderScene={this.renderScene}
initialRoute={{
component: Feed,
}}
/>
<BottomMenu reloadFeedData={this.reloadFeedData} />
</View>
);
}
});
To answer my own question,
the issue was a bad componentWillReceiveProps which triggered on things it shouldnt.

Resources