listview disappeared after setState({ds}) - ios

I am confused with the rerender mechanism of listview.
Page 1 have render a listview with two item, then I click 'Add' button, navigate to another page, and add one item to Page 1's datasource, then navigator back.
What I expect to see is Page 1 with three item, but actually is Page 2, listview is disappeared. But if I use mouse/finger touch it, listView come out again with three item.
I have test it on my iphone and simulator
Page 1's source code:
class Market extends Component {
constructor(props) {
super(props)
this.state = {
dataSource: new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }),
}
}
componentDidMount() {
this.refreshListView(this.props.data)
}
componentWillReceiveProps(nextProps) {
this.refreshListView(nextProps.data)
}
refreshListView() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(data)
})
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}
refreshControl={
<RefreshControl/>
}
/>
)
}
const mapStateToProps = createSelector(
selectData(),
(data) => ({
data,
})
)
export default connect(mapStateToProps)(Market)

Just found something that has fixed mine! try adding removeClippedSubviews={false} to your ListView
https://github.com/facebook/react-native/issues/8607#issuecomment-231371923

Related

contextmenu event not working in webview [Electron]

I'm using Electron with React+Typescript but this can be tested using just Electron+Javascript. The dom-ready event is working, I can see it in the console, but the contextmenu event is not triggered when I right click.
export default Main() {
const browserRef = useRef<WebViewProps>(null);
useEffect(() => {
const domReady = () => {
console.log("Dom is ready!");
};
const contextMenu = () => {
console.log("Context menu is working!");
};
if(browserRef.current) {
browserRef.current.addEventListener("dom-ready", domReady);
browserRef.current.addEventListener("contextmenu", contextMenu);
};
return () => {
if (browserRef.current) {
browserRef.current?.removeEventListener("dom-ready", domReady);
browserRef.current?.removeEventListener("contextmenu", contextMenu);
};
};
}, []);
return (
<webview ref={browserRef} src="https://www.google.com/" />
);
};
My contextmenu is not being called when I right click, this makes me think it doesn't fire when I right click but i don't know...

React Native Reload Screen A In Back action

I Have ScreenA To Click Next ScreenB Then back To Screen A Not Call Function componentWillMount()
ScreenA -> Next -> ScreenB -> Back() -> ScreenA
How to Reload Rout Screen in Back Action
Class ScreenA
import React from "react";
import { Button, Text, View } from "react-native";
class ScreenA extends Component {
constructor(props){
super(props)
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
})
}
}
componentWillMount() {
fetch(MYCLASS.DEMAND_LIST_URL, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId:'17'})
})
.then((response) => response.json())
.then((responseData) => {
if (responseData.status == '1') {
var data = responseData.data
this.setState({
dataSource: this.state.dataSource.cloneWithRows(data),
});
}
})
.done();
}
onPress = () => {
this.props.navigate("ViewB");
};
render() {
return (
<View>
<Text>test</Text>
<Button title="Next" onPress={this.onPress} />
</View>
);
}
}
Class ScreenB
import React from "react"
import { Button } from "react-native"
class ScreenB extends Component {
render() {
const {goBack} = this.props.navigation;
return(
<Button title="back" onPress={goBack()} />
)
}
}
Class ScreenA
import React from "react";
import { Button, Text, View } from "react-native";
class ScreenA extends Component {
constructor(props){
super(props)
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
})
}
}
componentWillMount() {
this.getData()
}
getData() {
fetch(MYCLASS.DEMAND_LIST_URL, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId:'17'})
})
.then((response) => response.json())
.then((responseData) => {
if (responseData.status == '1') {
var data = responseData.data
this.setState({
dataSource: this.state.dataSource.cloneWithRows(data),
});
}
})
.done();
}
onPress = () => {
this.props.navigate("ViewB", { onSelect: this.onSelect, getData: () => this.getData() });
};
render() {
return (
<View>
<Text>test</Text>
<Button title="Next" onPress={this.onPress} />
</View>
);
}
}
Class ScreenB
class ScreenB extends Component {
componentWillUnmount() {
this.props.navigation.state.params.getData()
}
render() {
const {goBack} = this.props.navigation;
return(
<Button title="back" onPress={goBack()} />
)
}
}
As react-navigation using stack. When we navigate to another screen, current screen remain as we have, another screen show on current screen. That means competent is still there. Component will reload (recycle) only if component creating again but at this point component will not change. We can reload data and re-render data.
By default react navigation not providing any api for onBack event. But we can achieve our goal by some tricks.
Way 1
use one function to handle onBack event and pass it to navigated screen
class ScreenA extends Component {
onBack() {
// Back from another screen
}
render() {
const { navigation } = this.props
return (
<Button title="Open ScreenB" onPress={() => navigation.navigate('ScreenB', { onBack: this.onBack.bind(this) })} />
)
}
}
// In this ScreenB example we are calling `navigation.goBack` in a function and than calling our onBack event
// This is not a safest as if any device event emmit like on android back button, this event will not execute
class ScreenB extends Component {
goBack() {
const { navigation } = this.props
navigation.goBack()
navigation.state.params.onBack(); // Call onBack function of ScreenA
}
render() {
return (
<Button title="Go back" onPress={this.goBack.bind(this)} />
)
}
}
// In this ScreenB example we are calling our onBack event in unmount event.
// Unmount event will call always when ScreenB will destroy
class ScreenB extends Component {
componentWillUnmount() {
const { navigation } = this.props
navigation.state.params.onBack();
}
render() {
return (
<Button title="Go back" onPress={() => this.props.navigation.goBack()} />
)
}
}
Way 2
Try react-navigation listener https://reactnavigation.org/docs/en/navigation-prop.html#addlistener-subscribe-to-updates-to-navigation-lifecycle
We have some limitation in this. We have blur and focus event. You can put your logic on focus. Whenever you will back from another screen, ScreenA will focus and we can execute our logic. But there is one issue, this will execute every time when we got focus whatever first time or we minimize and reopen application.
Way 3
https://github.com/satya164/react-navigation-addons#navigationaddlistener
I'm not sure about this way, I didn't tried.

React component + jQuery draggable thinks every element is the same object

Disclaimer: React noob over here - I apologize if I'm bastardizing the way it's meant to be used.
I'm trying to use jQuery's draggable API in conjunction with React components. However, whenever I drag the list item, the object associated to the drag event is the same.
class CompanyListItem extends React.Component {
componentDidMount() {
that = this
$("[role='draggable']").draggable({
start: (e, ui)=> {
that.props.dragHandler(event, ui, this);
},
revert: true
});
}
render() {
return (
<li className="draggable company" id={this.props.company.id} role="draggable">
<span>{this.props.company.name}</span>
</li>
);
}
}
class CompanyList extends React.Component {
render() {
var rows = []
_.each(this.props.companies, (company) => {
rows.push(<CompanyListItem company={company} dragHandler={this.props.dragHandler} key={company.id}/>);
});
return (
<ul> Select
{rows}
</ul>
);
}
}
For example, the component rendered below:
The object tied to both list elements is the "HopShop" (the first rendered list item)
Everytime you call $("[role='draggable']").draggable(...) you reset the other components. You should call it on the id instead.
componentDidMount() {
that = this
$("#" + this.props.company.id).draggable({
start: (e, ui)=> {
that.props.dragHandler(event, ui, this);
},
revert: true
});
}

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...

How to set state to child component in React Native

I'm trying to create a React Native app for managing categories and items. It uses React Native Store to save all local data. The app should not and will not use an internet API. It will be local and offline only.
I managed to push items to the DB when calling the addCategory(title) function but it does not update the state inside categories.js
Since there is a spectacular lack of documentation concerning the state in react-native, I wondered if anyone here knows how to make it update through the listview and when changing from the categories to the items to details Components.
I'm using React Native 0.26
index.ios.js:
... // default imports
import Store from 'react-native-store'
const categorieslist = require('./categories')
const DB = {
'categories': Store.model('category'),
'items': Store.model('item')
}
class testapp extends Component {
componentDidMount() {
this.reload()
}
reload() {
DB.categories.find().then(resp => this.setState({
categories: resp
}))
}
addCategory(title) {
var newTitle = title;
DB.categories.add({
title: newTitle,
games: []
})
this.reload()
}
render() {
return (
<View>
<NavigatorIOS
...
initialRoute={{
component: categorieslist,
title: 'Categories',
rightButtonTitle: 'New',
onRightButtonPress: () => {this.addCategory('title')},
passProps: {
categories: this.state.categories
}
}}
/>
</View>
)
}
}
... // AppRegistry registerComponent and stuff
and categories.js:
... // default imports
class categories extends Component {
constructor(props) {
super(props)
var ds = new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2})
this.state = {
dataSource: ds.cloneWithRows(props.categories)
}
}
render() {
if (this.state.dataSource.getRowCount() === 0) {
... // shows 'no categories' screen
} else {
return(
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <View><Text>{rowData}</Text></View>}
/>
)
}
}
}
module.exports = categories
State in react-native is the same as react (web). So you can refer to the documentation here: https://facebook.github.io/react/docs/component-api.html
state is local to each component. In your reload method you setState to this which is the testapp Component and not the categories Component.
You must pass your state to the categories component via props.

Resources