PanHandler doesn't detect touch events inside Modal - javascript

I have added a PanHandler to check if my app goes idle, and when it does, it shows a warning. The PanHandler works for all the content inside the page, but not when a Modal is shown. The touch events inside the modal seems to be ignored. The code is as follows.
render() {
return (
<View style={styles.mainContainer}
collapsable={false}
{...this.pagePanResponder.panHandlers}>
{this.addModal()}
//Content
</View>
);
addModal() {
return (
<Modal
animationType="fade"
transparent={true}
visible={this.state.addModalVisible}>
//Content
</Modal>
)
}
pageLastInteraction = new Date();
pagePanResponder = {};
componentWillMount() {
this.panResponderSetup();
}
panResponderSetup() {
this.pagePanResponder = PanResponder.create({
onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder,
onResponderTerminationRequest: () => false,
onStartShouldSetPanResponderCapture: () => false,
onMoveShouldSetPanResponderCapture: () => false,
onPanResponderTerminationRequest: () => true,
onShouldBlockNativeResponder: () => false,
});
this._maybeStartWatchingForInactivity();
}
_maybeStartWatchingForInactivity = () => {
if (this._inactivityTimer) {
return;
}
this._inactivityTimer = setInterval(() => {
if (
new Date() - this.pageLastInteraction >= TIME_TO_WAIT_FOR_INACTIVITY_MS
) {
this._setIsInactive();
}
}, INACTIVITY_CHECK_INTERVAL_MS);
};
_setIsActive = () => {
this.pageLastInteraction = new Date();
if (this.state.timeWentInactive) {
this.setState({ timeWentInactive: null, isIdle: false });
}
this._maybeStartWatchingForInactivity();
};
_setIsInactive = () => {
console.log("PAGE WENT IDLE");
if (this.idleWarningModal != null) {
this.setState({
addModalVisible: false
}, () => {
this.idleWarningModal.showModal();
});
}
this.setState({ timeWentInactive: new Date(), isIdle: true });
clearInterval(this._inactivityTimer);
this._inactivityTimer = null;
};
handleStartShouldSetPanResponder = () => {
this._setIsActive();
return false;
};
handleMoveShouldSetPanResponder = () => {
this._setIsActive();
return false;
};
All the events in the main view seems to get covered by the PanHandler, except the content in the Modal. How to add the PanHandler to the Modal as well?
React Native Version - 0.55.4
React Version - 16.3.1

Well, stupid mistake.
onResponderTerminationRequest: () => this.handleMoveShouldSetPanResponder
This fixed the issue. As onPress events were ignored by my logic before.

Related

how to make proper default props with single objects

I have this code:
const setup = (props: SchemaModalProps = { isOpen: false, onClose: () => { }, row: {}, onSchemaChange: () => { }, updateSchema: () => { }, hasPermission: false }) => {
const wrapper: any = mount(<SchemaModal {...props} />);
const driver = new SchemaModalDriver(wrapper);
return driver;
};
and when I call the setup function I need to specify the inner object items like so:
const driver = setup({ isOpen: true, row: someTriggerConfiguration, onClose: () => { }, onSchemaChange: () => { }, updateSchema: () => { }, hasPermission: true });
how can I rewrite the code in such a way that if I do setup({isOpen:false}) it will only overwrite the isOpen and not the rest of them ( use their default values).
You can destructure the props object and declare the function like below:
const setup = ({
isOpen = false,
onClose = () => {},
row = {},
onSchemaChange = () => {},
updateSchema = () => {},
hasPermission = false
}: SchemaModalProps) => {
/**
* Your code
*/
return <></>;
};
Now setup({isOpen:false}) will only override the isOpen property.
You can use Object.assign() to combine two objects. This way only supplied new values will override default.
const setup = (props: SchemaModalProps) =>
{
SchemaModalProps = Object.assign({
isOpen: false,
onClose: () => { },
row: {},
onSchemaChange: () => { },
updateSchema: () => { },
hasPermission: false
}, SchemaModalProps || {});
const wrapper: any = mount(<SchemaModal {...props} />);
const driver = new SchemaModalDriver(wrapper);
return driver;
};
Based on previous questions I found this to work best:
const setup = ({ isOpen = false, onClose = () => { }, row = {}, onInfoChange = () => { }, hasPermission = false }) => {
const props: TriggerInfoModalProps = { isOpen, onClose, row, onInfoChange, hasPermission }
const wrapper: any = mount(<TriggerInfoModal {...props} />);
const driver = new TriggerInfoModalDriver(wrapper);
return driver;
};

Update tvWidget in charting component using hooks

I am adding the tradingview charting library into my project and am having troubles getting the chart to re-render when I change the selected symbol.
When the chart loads initially it was calling a componentDidMount to submit parameters to their chart component which returns the chart. This is the charting component and I have a list of securities beside it that update redux state for symbol when clicked.
what I want to do is force the chart to update when the state changes so the correct symbol is displayed.
It is the same issue mentioned in this question, but I'm using hooks instead of class based components and when I try to use useEffect as componentDidUpdate I am getting symbol undefined.
Update:: in other question they said to use something like this in componentDidUpdate
this.tvWidget.chart().setSymbol('BINANCE:' + this.props.selectedSymbol.name)
but I cannot figure out how to do something similar with hooks
charting.js
export function TVChartContainer(props) {
const [symbol, setSymbol] = useState(props.symbol);
const tvWidget = null;
useEffect(() => {
setSymbol(props.symbol)
}, [props.symbol])
const componentDidMount = () => {
// setSymbol(props.symbol)
const widgetOptions = {
symbol: symbol,
//symbol: 'BTC/USDT',
//symbol: 'BTC/USD', //getUrlVars()["symbol"],
datafeed: Datafeed,
container_id: 'tv_chart_container',
library_path: '/charting_library/',
locale: getLanguageFromURL() || 'en',
disabled_features: ['use_localstorage_for_settings'],
enabled_features: ['study_templates'],
charts_storage_url: props.chartsStorageUrl,
charts_storage_api_version: props.chartsStorageApiVersion,
fullscreen: false,
autosize: true,
width: '100%',
timezone: 'America/New_York',
client_id: 'Hubcap',
user_id: 'public_user_id',
auto_save_delay: 10,
theme: 'Light',
loading_screen: { backgroundColor: '#222222', foregroundColor: '#229712' },
custom_indicators_getter: indicators,
};
const tvWidget = new widget(widgetOptions);
// tvWidget = tvWidget;
const thisComponent = props;
tvWidget.onChartReady(() => {
tvWidget.headerReady().then(() => {
const button = tvWidget.createButton();
button.setAttribute('title', 'Click to show a notification popup');
button.classList.add('apply-common-tooltip');
button.addEventListener('click', () =>
tvWidget.showNoticeDialog({
title: 'Notification',
body: 'TradingView Charting Library API works correctly',
callback: () => {
console.log('Noticed!');
},
})
);
button.innerHTML = '';
// thisComponent.getPattern(); //might need to uncomment later
tvWidget
.chart()
.onIntervalChanged()
.subscribe(null, function (interval, obj) {
console.log('On interval change');
thisComponent.getPattern();
});
tvWidget
.chart()
.onSymbolChanged()
.subscribe(null, function (symbolData) {
console.log('Symbol change ' + symbolData);
// thisComponent.getPattern();
});
// tvWidget.chart().createStudy('Strange Indicator', false, true);
// tvWidget.chart().createStudy('ESS Indicator', false, true);
// tvWidget.chart().createStudy('ESL Indicator', false, true);
// tvWidget.chart().createStudy('EPS Indicator', false, true);
// tvWidget.chart().createStudy('EPL Indicator', false, true);
// tvWidget.chart().createStudy('ETS Indicator', false, true);
// tvWidget.chart().createStudy('ETL Indicator', false, true);
});
});
};
const componentWillUnmount = () => {
if (tvWidget !== null) {
tvWidget.remove();
tvWidget = null;
}
};
// useEffect(() => {
// componentDidMount();
// // getPattern();
// // drawPattern();
// // // removeAllShape();
// return () => {
// componentWillUnmount();
// }
// }, [symbol])
useEffect(() => {
setSymbol(props.symbol)
componentDidMount();
// getPattern();
// drawPattern();
// // removeAllShape();
return () => {
componentWillUnmount();
}
}, []);
return <div id="tv_chart_container" className={'TVChartContainer'} />;
main page componenet
const TestPage = ({selected}) => {
const [symbol, setSymbol] = useState('AAPL');
useEffect(() => {
setSymbol(selected)
}, [selected])
return (
<div>
<TVChartContainer symbol={symbol} />
</div>
);
}
const mapStateToProps = (state) => {
return {
selected: state.Watchlist.stock.selected,
}
}
export default connect(mapStateToProps)(TestPage)
watchlist
const Security = ({index, name, stocks, selected}) => {
const dispatch = useDispatch();
const [taskName, setTaskName] =useState(name)
const [prevState, setPrevState] = useState(stocks)
const removeTask = (e) => {
e.stopPropagation()
setPrevState(stocks)
dispatch(removeStock(index))
}
const selectAStock = () => {
dispatch(stockSelected(name))
}
useEffect(() => {
setPrevState(stocks)
}, [])
useEffect(() => {
if(prevState !== stocks) dispatch(updateWatchlist(stocks, selected))
}, [stocks])
return (
<Row className="list-group-item">
<div className="item-titles" onClick={() => selectAStock()}>
{name}
</div>
<button onClick={(e) => removeTask(e)} className="remove-item">
<i className="glyphicon glyphicon-remove"></i>
</button>
</Row>
);
}
const mapStateToProps = (state) => {
return {
stocks: state.Watchlist.stock.watchlist,
}
}
export default connect(mapStateToProps, {removeStock, updateWatchlist, stockSelected})(Security);
this.tvWidget?.setSymbol("BINANCE", "5" as ResolutionString, () => null)
The setSymbol accept 3 parameters.
(symbol: string, interval: ResolutionString, callback: EmptyCallback): void
Symbol: which is a string
Interval: which is of type ResolutionString. ("5" as ResolutionString) use the 'as' to prevent error)
callback: just an empty callback
on componentDidUpdate() you can update the tradingView Widget with the following parameters.

One function does not work inside another in React Native

I have two functions which I run with a button:
this.state = {
myLimit: this.props.limit.lim,
modalOpen: false,
}
submit = () => {
// this sends state to Redux reducer
let lim = {'lim':this.state.myLimit};
this.props.updateLimit(lim);
// this sends update state for toggle in Parent component
this.props.changeToggle(false);
// function open Modal for 1,5 second like "Success"
showModal = () => {
this.setState({
modalOpen: true
});
this.timeout = setTimeout(() => {
this.setState({
modalOpen: false
})
}, 1500);
}
render(){
return (
<View style={styles.container}>
//some code
<Button onPress={ this.submit } onPressIn={ this.showModal } title='submit' />
<MyPopup visible={this.state.modalOpen}>
<View style={styles.modal}>
<Text style={styles.text}>The limit successfully changed</Text>
</View>
</MyPopup>
</View>
)
}
Parent component
//Parent component
...
this.state = {
openLimit: false,
}
toggle(toggler) {
let togglerStatus = this.state[toggler];
this.setState({
[toggler]: !togglerStatus
});
}
// run changing toggle from child 'Limit'
changeToggle = (val) => {
console.log(val)
this.setState({
openLimit: val
})
};
return(
//some code
<Child changeToggle={this.changeToggle}/>
)
It works, but not always good enough. Sometimes the submit function does not send state to Redux reducer in this this.props.updateLimit(lim) and/or not change toggle state this this.props.changeToggle(false).
So I am tried to combine it in one function:
combineFunc = () => {
// this works
// this sends state to Redux reducer
let lim = {'lim':this.state.myLimit};
this.props.updateLimit(lim)
// this part does not work
// function open Modal for 1,5 second like "Success"
this.setState({
modalOpen: true
});
this.timeout = setTimeout(() => {
this.setState({
modalOpen: false
})
}, 1500);
// this works
// this sends update state for toggle in Parent component
this.props.changeToggle(false);
}
render(){
return (
<View style={styles.container}>
//some code
<Button onPress={this.combineFunc} title='submit' />
</View>
)
}
But in this case it does not change -> this.setState({modalOpen: true}) and Modal does not open at all.
Why does it happen? Is it possible to set some order to run?
I think the problem is in setTimeout, but I need it for a popup.
Any suggestions?
Ciao the problem is in setTimeout as you said. You know this.setState is async. But it has a callback, so try this:
this.setState({
modalOpen: true
}, () => {
this.timeout = setTimeout(() => {
this.setState({
modalOpen: false
})
}, 1500);
});

How can I display a modal after a delay?

I'm trying to load a modal 2 seconds after the page has been loaded. I tried setting the state on componentDidUpdate but I keep on getting active: undefined The active props determines the visibility of the modal on the page. I tried toggling it to true on browser on the react tool and my modal shows up. I'm just not sure how to load to it 2 seconds after the page loads up.
state = { show: true };
showModal = () => {
this.setState({ show: true });
};
closeModal = () => {
this.setState({ show: false });
};
render() {
const { showModal } = this.state;
return (
<React.Fragment>
....
<Modal.ModalAnimator active={showModal} onClose={this.closeModal}>
<Modal.ModalWithCross
onClose={this.closeModal}
>
<h3>Are you interested in any other Additions?</h3>
<Section>
<p>Hit “notify concierge” and we’ll be in touch shortly.</p>
</Section>
</Modal.ModalWithCross>
</Modal.ModalAnimator>
</React.Fragment>
)
}
When destructuring the state, you write showModal instead of the actual state field name show. So your first lines in the render function should read:
render() {
const { show } = this.state;
return (
<React.Fragment>
...
<Modal.ModalAnimator active={show} onClose={this.closeModal}>
...
Please try this.
state = { show: true };
closeModal = () => {
this.setState({ show: false });
};
componentDidMount() {
setTimeout(() => {
this.setState({ show: true });
}, 2000);
}
render() {
const { showModal } = this.state;
return (
let model = null;
if (this.state.show) {
let model = (
<Modal.ModalAnimator active={showModal} onClose={this.closeModal}>
<Modal.ModalWithCross
onClose={this.closeModal}
>
<h3>Are you interested in any other Additions?</h3>
<Section>
<p>Hit “notify concierge” and we’ll be in touch shortly.</p>
</Section>
</Modal.ModalWithCross>
</Modal.ModalAnimator>
)
}
<React.Fragment>
....
{model}
</React.Fragment>
)
}

Detecting clicks outside a div and toggle menu

I have a toggle menu and I'm trying detect the click events outside of a menu to be able to close the menu, I can close the menu when user clicks outside of the menu, however to open the menu again you would have to click on it twice, does anyone know what I have to do to fix that, (the menu should open with one click)
const RightMenu = ({ t, history }) => {
let [menuOpen, setMenuOpen] = useState(false);
const menuDiv = useRef({});
const toggleMenu = useRef();
useEffect(() => {
window.addEventListener("click", () => {
if ((menuDiv.current.style.display = "block")) {
menuDiv.current.style.display = "none";
}
});
return () => {
window.removeEventListener("click", () => {});
};
}, []);
const handleClick = e => {
e.stopPropagation();
if (menuOpen === false) {
menuDiv.current.style.display = "block";
setMenuOpen(true);
}
if (menuOpen === true) {
menuDiv.current.style.display = "none";
setMenuOpen(false);
}
};
return (
<div>
<div
id="menu"
ref={menuDiv}
style={{
display: "none"
}}
>Menu items</div>
<div
className="text-center"
ref={toggleMenu}
onClick={e => handleClick(e)}
> Menu Button</div>
)
}
const RightMenu = ({ t, history }) => {
let [menuOpen, setMenuOpen] = useState(false);
useEffect(() => {
window.addEventListener("click", () => {
setMenuOpen(prevState => {
return !prevState
})
});
return () => {
window.removeEventListener("click", () => {});
};
}, []);
const handleClick = () => {
e.stopPropagation();
setMenuOpen(!menuOpen);
};
return (
<div>
{menuOpen && (<div
id="menu"
>Menu items</div>)}
<div
className="text-center"
onClick={handleClick}
> Menu Button</div>
)
You do not need refs to achieve this, u can conditionally render the menu based on the menuOpen state like in the example provided.
You are not actually removing the event listener from window when your component unmounts. The second argument to the removeEventListener should be a reference to the same function you added with addRemoveListener. E.g.
useEffect(() => {
const closeMenu = () => {
if ((menuDiv.current.style.display = "block")) {
menuDiv.current.style.display = "none";
}
};
window.addEventListener("click", closeMenu);
return () => {
window.removeEventListener("click", closeMenu);
};
}, []);
#Watch('openDropdown')
openHandler(newValue) {
newValue ? this.setOpen() : this.setClose();
}
componentWillLoad() {
document.addEventListener('click', this.handleClick, false)
}
componentDidUpdate() {
//
if (this.dropdownNode != null && this.collapsableIconNode != null) {
this.dropdownNode.style.top = this.collapsableIconNode.offsetTop + 20 + 'px'
this.dropdownNode.style.left = this.collapsableIconNode.offsetLeft - 11 + 'px'
}
}
componentDidUnload() {
document.removeEventListener('click', this.handleClick, true)
}
handleClick = (e) => {
if (this.collapsableIconNode.contains(e.target)) {
this.openDropdown = true;
}
else {
this.handleClickOutside()
}
}
handleClickOutside() {
this.openDropdown = false;
}
<span ref={collapsableIconNode => this.collapsableIconNode = collapsableIconNode as HTMLSpanElement} id="collapseIcon" class="collapsable-icon" onClick={() => this.setOpen()}>
This is Written in StencilJS, Logic is same,It is similar to React!!
I was dealing with the same problem recently. I ended up using this lib - https://github.com/Andarist/use-onclickoutside
Worked perfectly for me. Minimal effort. It covers all the edge cases.
Maybe you should give it a try.

Categories