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
Related
I have a set of movie cards coming from an API. When I click on the movie a modal opens with a button in it. Since there is a lot of movies, I would like to know which button was clicked on, to preserve that button's state as
'Added to Watchlist', if it was clicked on. How could I achieve that?
Pass a movie name/movie ID as a prop to the button. Id can be stored as a state in the parent component.
//movieCard is API response
state = {
movideId: '',
isOpneModal: false,
}
openModal = (cardId) => setState({ movideId: cardId })
render() {
return (
<Fragment>
{movieCard.map(card =>
<MovieCard cardDetails={card} onClick="()=>openModal(card.Id)") />
)}
{isOpneModal && <Modal movideId={this.state.movideId} />}
</Fragment>
)
I'm having issues getting the Detail Panel of Material Table to re-render when there is a change to the tab selection of a Material-UI tab component. What I'm expecting to happen is when I select the second tab in the tab list, the styling and component should re-render to reflect that in the DOM. As of right now that isn't happening. The value property is being updated, but the DOM is never being re-rendered from the value change. The value property I'm passing to the handleChange function is an index. So for 3 tabs, there would be 3 different values (0, 1, 2)
You can see from this example , when you click a subsequent tab in the AppBar, the state is updated and changed automatically. I'm able to effectively change the 'value' property by clicking a different tab, but the Detail Panel is never re-rendered and the first tab is always selected.
This PR had a similar issue but I wasn't able to get any of the answers to work for my need.
import AppBar from '#material-ui/core/AppBar'
import Tabs from '#material-ui/core/Tabs'
import Tab from '#material-ui/core/Tab'
function TableComponent(props) {
const [value, setValue] = React.useState(0)
const handleChange = (event, newValue) => {
setValue(newValue)
}
function getVersionsTabs (rowData) {
const versions = Object.keys(rowData.versions)
var versionList = versions.map(function (name, index) {
const version = rowData.versions[name]
return <Tab key={index} label={version.versionAlias} />
})
return versionList
}
return (
<MaterialTable
...otherProps
detailPanel={
rowData => {
return (
<div>
<AppBar position='static' color='default'>
<Tabs value={value} onChange={handleChange} indicatorColor='primary' textColor='primary'>
{getVersionsTabs(rowData)}
</Tabs>
</AppBar>
</div>
)
}
/>
)
}
Any help is greatly appreciated!
I have two component: 1:StudentList 2: Major in react and antd.
StudentList Component rendered a list of students.
Major Component made a list of majors that you can pick them. After selecting major, the selected major title display on the top of the students list. and the list will be filtered according to the selected major.
This is StudentList component contain Major component:
class StudentList extends Component {
render(){
return(
<>
<Major/>
<h5>20 student found in <a>selected major</a></h5>
<List>
//this is the list of students and is not related to this question
</List>
</>);
}
}
This is Major Component with a filter button to open the popover:
class Major extends Component {
render() {
return (
<Popover
trigger="click"
content={content} //list of majors
>
<Button>
<FilterOutlined /> Select major to filter
</Button>
</Popover>
);
}
}
When I click on the Select major to filter button, the popover open to select majors. I want to change the code in order to open this popover from two place:
1- click on Select major to filter button in the Major component
2- click on selected major in the title in StudentList component.
Notice: I want to open the same popover in the same place (similar to when I click on Select major to filter button)
Maybe it could handle with state and handleVisibleChange function. but I don't know how to handle it from 2 components. I glad to hearing your solutions.
You can use the visible and onVisibleChange property from Antd's tooltip because they are used by the PopOver as well. You can find an easy example from Andt how to control a PopOver by visible in the docs.
To get the button click you can use onClick from antd's Button Api.
The desired example using React Components:
class Major extends Component {
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.value !== prevProps.value) {
this.setState({ visible: this.props.value });
}
}
state = {
visible: false
};
hide = () => {
this.setState({
visible: false
});
};
handleVisibleChange = visible => {
this.setState({ visible });
// this.props.onChange(visible); // add me to open popover on every click on studenlist
};
render() {
return (
<Popover
trigger="click"
content={<a onClick={this.hide}>Close</a>}
visible={this.state.visible}
onVisibleChange={this.handleVisibleChange}
>
<Button>Select major to filter</Button>
</Popover>
);
}
}
class StudentList extends Component {
state = {
visible: false
};
onClick = () => {
this.setState({ visible: !this.state.visible });
};
render() {
return (
<>
{/* <Major value={this.state.visible} onChange={setVisible} /> */}
<Major value={this.state.visible} />
<h5>
20 student found in <a>selected major</a>
</h5>
<Button onClick={this.onClick}>Select major from Studenlist</Button>
</>
);
}
}
Component example as a CodeSandBox.
Here is a simple example for your request using react hooks and simple buttons to open the PopOver:
function Major({ value, onChange }) {
const [visible, setVisible] = useState(value);
useEffect(() => {
value && setVisible(value);
}, [value]);
const hide = () => setVisible(false);
const handleVisibleChange = visible => {
setVisible(visible);
onChange(visible);
};
return (
<Popover
trigger="click"
content={<a onClick={hide}>Close</a>}
visible={visible}
onVisibleChange={handleVisibleChange}
>
<Button>Select major to filter</Button>
</Popover>
);
}
function StudentList() {
const [visible, setVisible] = useState(false);
const onClick = () => {
setVisible(true);
};
return (
<>
<Major value={visible} onChange={setVisible} />
<h5>
20 student found in <a>selected major</a>
</h5>
<Button onClick={onClick}>Select major from Studenlist</Button>
</>
);
}
The depended working CodeSandBox.
Edit1: Added React Component example.
I have a Flatlist which works like a To Do list with a filter for “ToDo” and “Upcoming”. When a user swipes to complete the item, it gets hidden from the list by changing a displayIndex attribute. I would like this to reload the list after the swipe or before the user selects “Upcoming”. After reading through other stack overflow answers I have tried adding extraData={this.state} (and creating a this.state.refresh property which changes after every swipe) to the Flatlist and I also ensured that the list items themselves are React.Components and not PureComponents. I have also tried two ways to hide the ListItems, conditionally rendering them and conditionally changing the style to hidden. Still, I am not seeing any change in my Flatlist.
Below is some partial code to see if there are any gotchas I missed:
In the MainScreen.js
async _addCompletion(myItem) {
//Lots of business logic and after it's done the below code activates
await AsyncStorage.setItem(myItem.key, JSON.stringify(myItem));
await this._updateData();
this.setState({ refresh: !this.state.refresh });
}
render() {
const buttons = ['To Do', 'Upcoming'];
const { displayModeIndex } = this.state;
return (
<View>
<ButtonGroup
onPress={this._updateButtonIndex}
buttons={buttons}
selectedIndex={displayModeIndex}
/>
<FlatList
displayMode={this.state.displayModeIndex}
data={this.state.data}
extraData={this.state}
scrollEnabled={this.state.scrollEnabled}
renderItem={({ item }) => (
<MyListItem
myListItem={item}
addCompletion={this._addCompletion}
displayIndex={this.state.displayModeIndex}
setScrollEnabled={this._setScrollEnabled}
navigation={this.props.navigation}
/>
)}
/>
</View>
);
}
In MyListItem.js
_displayMyItem {
//Logic that determines whether to display a myItem based on several factors. I can confirm this works after refreshing.
}
_hideMyItem = () => {
Animated.timing(this.containerHeight, {
toValue: 0,
}).start(() => {
this.setState({ hidden: true });
});
};
render () {
const {myItem} = this.state;
//Other code that determines how the list item looks depending on myItem data.
return (
//I have also tried to return null if this._displayMyItem(this.state.myItem) returns false
<View style={!this._displayMyItem(this.state.myItem) && { display: 'none' }}>
<Swipeable
onPress={this._onPressRow}
setScrollEnabled={this.props.setScrollEnabled}
addCompletion={this.props.addCompletion}
hideMyItem={this._hideMyItem}
myItem={this.state.myItem}
>
//Other JSX Code
</View>
)
}
The Swipeable is a custom component that calls addCompletion after a swipe and _hideMyItem after everything is done. It is not a PureComponent either.
There's a lot going on here, so I've only included code that seems relevant. I can add more if needed. The addCompletion method is a long
would help some captures...
When you swipe the item , it's just empty right?, if it leaves an empty space try this way of conditional rendering , idk if it would work.
in MyListItem.js
render () {
const {myItem} = this.state;
//Other code that determines how the list item looks depending on myItem data.
return (
//I have also tried to return null if this._displayMyItem(this.state.myItem) returns false
{!this.state.hidden?
<View style={!this._displayMyItem(this.state.myItem) && { display: 'none' }}>
<Swipeable
onPress={this._onPressRow}
setScrollEnabled={this.props.setScrollEnabled}
addCompletion={this.props.addCompletion}
hideMyItem={this._hideMyItem}
myItem={this.state.myItem}
>
//Other JSX Code
</View>:null}
)
}
wich checks if this.state.hidden is false , returns the component, else, returns null
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.