How do I close a React Native Modal? - javascript

I am currently running into an issue where I can open my react-native modal just fine but once it's open I can't seem to close it. I just started using react-native about three weeks ago so I am extremely new to this.
I have tried implementing solutions that I've found online but nothing seemed to work for me. The opening functionality is great and seems to be working perfectly but when it comes to closing the modal none of the things I've tried have seemed to give the modal that ability. I have not been able to find a solid solution for my exact problem anywhere!
This is how I am opening the modal.
constructor(props) {
super(props);
this.state = {
refreshing: false,
display: false
};
}
triggerModal() {
this.setState(prevState => {
return {
display: true
}
});
}
<View>
<Button onPress = { () => this.triggerModal() } title = "Open Modal"></Button>
<DisplayModal display = { this.state.display } />
</View>
This is the modal itself, I am trying to use a button to close it.
import React from 'react'
import { Modal, View, Image, Text, StyleSheet, Button } from 'react-native';
const DisplayModal = (props) => (
<Modal visible={ props.display } animationType = "slide"
onRequestClose={ this.display }>
<View>
<Button title="close" onPress = { () => !props.display }></Button>
</View>
</Modal>
)
export default DisplayModal;
As my familiarity with react-native is limited, it has been difficult wrapping my head around how some aspects of the framework function... I'm probably just making a dumb mistake somewhere in the code.
I appreciate any help with this problem!

You've almost got it, however we can make a few tweaks to get it working as you want.
As your DisplayModal has no state of its own, the state must be controlled by its parent component. So with that in mind we can do the following. Firstly pass an additional prop called closeDisplay to the DisplayModal. We're going to pass a function that sets the display property in state to false.
<DisplayModal
display={this.state.display}
closeDisplay={() => this.setState({display: false})} // <- we are passing this function
/>
Then in our DisplayModal component we are going to call that function to close the modal. So your DisplayModal component should look like this:
const DisplayModal = (props) => (
<Modal
visible={ props.display }
animationType = "slide"
onRequestClose={ this.display }>
<View>
<Button
title="close"
onPress = { () => props.closeDisplay() }> // <- here we call the function that we passed
</Button>
</View>
</Modal>
)
Notice that the onPress function of the Button in the DisplayModal component, we are calling the function closeDisplay(). This function then sets the state in the parent component, which in turn gets passed back down to the DisplayModal component causing it to hide.

Related

React Parent/Child State Change Design Question

I have a component with two children, one of them is a button that toggles a state (modalVisible) that decides whether the other child, a modal, is visible.
I'm having trouble sharing the on/off state across the parent and the modal child. I tried keeping the state in the parent and then passing it as a prop to the child, but it wasn't rerendering the child everytime the parent state changed.
<CommentsModal visible={modalVisible} />
Inside CommentsModal.js...
import Modal from 'react-native-modal';
...
const CommentsModal = ({visible}) => {
const [modalVisible, setModalVisible] = useState(visible);
...
return <Modal visible={modalVisible} />
}
I considered keeping the state entirely in the parent, without passing it into CommentsModal, like so:
function renderModal() {
if (modalVisible) {
return <CommentsModal visible={true} />
} else {
return <View />
}
}
But I realized that there has to be a state inside CommentsModal because I need an "X" button that toggles the modal off.
I'm not sure what the best way to do this is... I could do redux, but since there is a dynamic number of these modals; I don't want my store to be that complicated. The only way I can think of is to move all of the modal code into the parent component, then they can share states easily, but it seems dirty to me. Does anyone have a solution?
Your intuition to keep the state in the parent component is correct. To implement the x button all you need is to pass a onClose prop to the modal which would be a function that sets modalVisible to false. so you'll end up with something like this:
// parent component
const ParentComponent = () => {
const [modalVisible, setModalVisible] = useState(false);
const openModal = () => setModalVisible(true);
const closeModal = () => setModalVisible(false);
return (
<div>
<CommentsModal visible={modalVisible} onClose={closeModal} />
<button onClick={openModal}>open the modal</button>
<p>other children here...</p>
</div>
)
}
// CommentsModal
const CommentsModal = (props) => (
<Modal visible={props.visible}>
<button onClick={props.onClose}>X</button>
<p>more modal content here...</p>
</Modal>
)

Material-table: Is it possible to open detailsPanel by default?

I'm trying to create a subtable of the main React Material-Table.
Everything is working properly as it should work, details panel (subtable) is showing on toggle icon press.
Are there any ways to show it opened by default? I mean to remove the toggle icon and show the detailPanel right from the component render?
Here is how my mat-table looks like (I didn't want to insert the whole component code, cause it will be too much code, full code is in the sandbox):
<MaterialTable
icons={tableIcons}
tableRef={tableRef}
columns={tableColumns}
data={tableData}
onRowClick={(evt, selectedRow) =>
setSelectedRow(selectedRow.tableData.id)
}
title="Remote Data Example"
detailPanel={detailSubtable}
options={{
rowStyle: rowData => ({
backgroundColor:
selectedRow === rowData.tableData.id ? "#EEE" : "#FFF"
})
}}
/>
And a link to the Codesandbox
As per knowledge, there is not any proper way or props to achieve this but you can do native DoM manipulation.
Provide custom Icon in DetailPanel icon with some unique ID like this:
DetailPanel: forwardRef((props, ref) => (
<div id="my-id" style={{ display: "none" }}>
<ChevronRight {...props} ref={ref} />
</div>
)),
Now, On componentDidMount find this element and trigger a click event on it and hide parent node like this
useEffect(() => {
const myToggler = document.getElementById("my-id");
if (!!myToggler) {
myToggler.click();
myToggler.parentNode.style.display = "none";
}
}, []);
here is the working sandbox link forked from yours, let me know if I am missing something.
If you see the source code There is a props called defaultExpanded which should work but there is an open issue which is causing the issue of not opening the panel by default.
To make it work (until the issue is fixed), you can imperatively modify the material-table's component the state in the useEffect
Like this
useEffect(() => {
tableRef.current.state.data = tableRef.current.state.data.map(data => {
console.log(data);
data.tableData.showDetailPanel = tableRef.current.props.detailPanel;
return data;
});
}, []);
Working demo of your code
This solution works for any number of rows/detailsPanel.

New react native element is not accessible

I'm working on react-native project (main target is iPhone 6) and got some problems with including new elements in accessibility chain. For some reasons Voice Over does not update when new element appears after re-rendering. Hidden button does not appear in accessibility chain after running showButton() method. It becomes visible, but iOS Voice Over does not see it. The problem occurs only when app does something asynchronously. Here is my code:
export default class SmartView extends Component {
constructor(props) {
super(props)
this.state = {
showButton: false,
}
}
showButton = () => {
setTimeout(() => {
this.setState({ showButton: true })
}, 500)
}
render() {
const { showButton } = this.state
return (
<View style={style.root}>
<Button
onPress={this.showButton}
accessibilityRole="button"
accessibilityTraits="button"
accessibilityLabel="appeared"
accessible
simple
>
<Text>Appeared</Text>
</Button>
{showButton && (
<Button
accessibilityRole="button"
accessibilityTraits="button"
accessibilityLabel="appeared"
accessible
simple
>
<Text>Hidden</Text>
</Button>
)}
</View>
)
}
}
So, if I remove setTimeout and do state updating in current js stream, everything work fine. Is there any possibility to make something like VoiceOverReload()?
I use: react-native v0.59.9 and iPhone 6, software version 12.4
Thanks.
Below demo works fine, probably your custom Button component has issues
import React, { useState } from 'react'
import { View, Text, TouchableOpacity } from 'react-native'
export default function Screen () {
const [showButton, setShowButton] = useState(false)
function handleShow () {
setTimeout(() => {
setShowButton(true)
}, 1000)
}
return (
<View style={{ padding: 40 }}>
<TouchableOpacity
onPress={handleShow}
accessibilityRole='button'
accessibilityTraits='button'
accessibilityLabel='This button label is long for demo'
accessible
>
<Text>Appeared</Text>
</TouchableOpacity>
{showButton && (
<TouchableOpacity
accessibilityRole='button'
accessibilityTraits='button'
accessibilityLabel='hidden'
accessible
>
<Text>Hidden</Text>
</TouchableOpacity>
)}
</View>
)
}
If your view is going to update and you need voice over to detect the change faster, the you can add the following trait to the parent view: frequentUpdates. This will be the equivalent of setting "Updates Frequently" on the accessibility properties in XCode, as explained in the following answer: Making dynamically updating content in a UITableView accessible for VoiceOver
This works for ReactNative 0.59, though its deprecated and I don't know how to do it in newer versions of RN.

Why is my modal not showing?

I'm new to react native.
My screen contains 5 buttons, each one opens the same <Modal>, but the <View> inside it will change depending on the button clicked.
If I click the first button, a text input will be shown into the modal.
If I click the second button, a switch will be shown into the modal.
I've made a modal component (Modal.tsx) :
export default class Modal extends Component {
constructor(props) {
super(props)
}
public render() {
return (
<View style={style.modal} >
{this.props.children}
<View>
)
};
}
// Specific modal implementation with TextInput
const ModalWithTextInput = props => (
<Modal>
<TextInput
value={props.someValue}
/>
<Modal>
)
// Specific modal implementation with Switch
const ModalWithSwitch = props => (
<Modal>
<Switch
value={props.someValue}
/>
<Modal>
)
And now in my 5-button-screen (ButtonsScreen.tsx), I open the right modal depending on the button clicked :
openTextModal = () => {
this.setState({ modalType: 'text' });
}
openSwitchModal = () => {
this.setState({ modalType: 'switch' });
}
These functions are called with, for example, onPress={this.openTextModal}
Finally, I render the modal, to be able to do something like :
<View>
{this.renderModal(modalType)}
</View>
As this :
renderModal = (type) => {
if (type === 'text') {
return <ModalWithTextInput someValue="default text" />
}
if (type === 'switch') {
return <ModalWithSwitch someValue={false}/>
}
}
When I try to open a modal with onPress={this.openTextModal}, nothing happens (no error, no warning).
Anyone can please help ? Thanks.
You need to extract modalType from state, in the render method of your component that displays the Modal.
Clicking the button, only set's state, you need to handle state in the component in order to trigger a refresh. A refresh of the render method will render your Modal changes; React 101.
render() {
const { modalType } = this.state;
return (
<View>
{this.renderModal(modalType)}
</View>
);
}
Based on the fact that this is pretty much my code from your other question. I strongly suggest you take a step back, learn the basic's of React rather than just asking people to piece together solutions which you do not understand. Otherwise the result is you learn very little and have code that you do not understand.

Closing a dropdown in React-Native on the next press

I have a ScrollView which contains multiple View components as children. Every child can have a dropdown.
Opening the dropdown on a button press works as it should.
Closing the dropdown on the same button or when something inside the dropdown is pressed works too.
Now I gave this a user and they just opened dropdowns over dropdowns without choosing anything or bothering to close the opened dropdowns.
Is there a way to set an event handler for the next press on the screen after a dropdown was opened so the dropdown closes if the user wants to do something different?
Edit
This is kinda my implementation:
const App = () =>
<View>
...
<List items={["one", "two", "three"]}/>
...
</View>
const List = props =>
<ScrollView>
{props.items.map(i => <ListItem name={i} key={i}/>)}
</ScrollView>
class ListItem extends React.Component {
state = { showDropdown: false};
handleDropdown = () =>
this.setState(state =>
({showDropdown: !state.showDropdown})
);
render() {
return <View>
<TouchableOpacity onPress={this.handleDropdown}>
<Text>{this.props.name}</Text>
</TouchableOpacity>
{this.state.showDropdown &&
<Dropdown>
<DropdownItem onPress={this.handleDropdown}/>
</Dropdown>
}
</View>
}
}
The dropdown closes when I click on an ListItem again or when I click on a DropdownItem.
But I also want it to close, when I click somewhere else on the screen, outside of the List component and its children.
Found a solution.
I added a PanResponder to my my apps root View and used this config:
const handlers = []
const addHandler = handler => handlers.push(handler);
const handleNextPress = (event, state) => handlers.forEach(h => h(event, state));
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: (event, state) =>
handleNextPress(event, state)
});
// somewhere else in the app
addHandler(this.closeMyDropdown);
The onStartShouldSetPanResponderCapture method is called every time a touch happens to check if the touch event should be captured. If it returns a falsy value the event isn't captured and hits the touchable components.
This method allows to sneak in handlers that can do things before the event hits the touchable components of the app or use a timeout to let things happen after the touchable components have reacted to the event.
Ideally, you'll want to make the dropdown a controlled component, with a prop that controls whether the dropdown is shown or not, and a callback the component calls when it's tapped.
You can then keep the active dropdown id in the parent component state, and when that is changed, all dropdowns should rerender
Something like:
class List extends Component {
state = {
openDrowdownId: null
}
openDropDown = id => {
this.setState({ openDropDownId: id });
}
closeDropDown = () => {
this.setState({ openDropDownId: null });
}
render() {
return (
<ScrollView>
{this.props.items.map(item => (
<View key={item.id}>
<Dropdown
isOpen={this.state.openDropDownId === item.id}
open={() => this.openDropDown(item.id)}
/>
</View>
))}
</ScrollView>
)
}
}
What you can do is to set a ref on the dropdown like
ref="dropdown"
and on the button press
onPress={() => this.refs.dropdown.blur(); }
This is like define an id and on button click to tell that this is is going to blur

Categories