Better solution to open the Menu when 3 dots are clicked in React Native - react-native

I am able to open menu when 3 dots icon is clicked for each item. But can the code be written in a better way..
Right now menu is getting created for each card item but ideally it would have been good to create single Menu View and dynamically associate it to some card where ever the 3 dots is clicked.
Expo Source Code Link
Code
export default class App extends React.Component {
constructor(props, ctx) {
super(props, ctx);
this.state = {
list: [
{ name: "Michael", mobile: "9292929292", ref: React.createRef() },
{ name: "Mason Laon Roah", mobile: "1232313233", ref: React.createRef() },
{ name: "Constructor", mobile: "4949494949", ref: React.createRef() },
{ name: "Rosling", mobile: "4874124584", ref: React.createRef() }
],
};
}
_menu = null;
hideMenu = () => {
this._menu.hide();
};
showMenu = (ref) => {
this._menu = ref;
this._menu.show();
};
render() {
const renderItem = ({ item, index }) => (
<ListItem
title={
<View>
<Text style={{ fontWeight: "bold" }}>{item.name}</Text>
<Text>{item.mobile}</Text>
</View>
}
subtitle={
<View>
<Text>445 Mount Eden Road, Mount Eden, Auckland. </Text>
<Text>Contact No: 134695584</Text>
</View>
}
leftAvatar={{ title: 'MD' }}
rightContentContainerStyle={{ alignSelf: 'flex-start'}}
rightTitle={this.getMenuView(item.ref)}
/>
);
return (
<View style={styles.container}>
<View style={{ flex: 1, marginTop: 30 }}>
<FlatList
showsVerticalScrollIndicator={false}
keyExtractor={(item, index) => index.toString()}
data={this.state.list || null}
renderItem={renderItem}
ItemSeparatorComponent={() => (
<View style={{ marginBottom: 5 }} />
)}
/>
</View>
</View>
);
}
getMenuView(ref) {
return (
<Menu
ref={ref}
button={<Icon onPress={() => this.showMenu(ref.current)} type="material" color="red" name="more-vert" />}
>
<MenuItem onPress={this.hideMenu}>Menu item 1</MenuItem>
<MenuItem onPress={this.hideMenu}>Menu item 2</MenuItem>
<MenuItem onPress={this.hideMenu} disabled>
Menu item 3
</MenuItem>
<MenuDivider />
<MenuItem onPress={this.hideMenu}>Menu item 4</MenuItem>
</Menu>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
},
});
Sample Output

As mentioned here, you can find an undocumented UIManager.java class that allows you to create Popups with its showPopupMenu method.
This currently works only for Android.
import React, { Component } from 'react'
import { View, UIManager, findNodeHandle, TouchableOpacity } from 'react-native'
import Icon from 'react-native-vector-icons/MaterialIcons'
const ICON_SIZE = 24
export default class PopupMenu extends Component {
constructor (props) {
super(props)
this.state = {
icon: null
}
}
onError () {
console.log('Popup Error')
}
onPress = () => {
if (this.state.icon) {
UIManager.showPopupMenu(
findNodeHandle(this.state.icon),
this.props.actions,
this.onError,
this.props.onPress
)
}
}
render () {
return (
<View>
<TouchableOpacity onPress={this.onPress}>
<Icon
name='more-vert'
size={ICON_SIZE}
color={'grey'}
ref={this.onRef} />
</TouchableOpacity>
</View>
)
}
onRef = icon => {
if (!this.state.icon) {
this.setState({icon})
}
}
}
Then use it as follows.
render () {
return (
<View>
<PopupMenu actions={['Edit', 'Remove']} onPress={this.onPopupEvent} />
</View>
)
}
onPopupEvent = (eventName, index) => {
if (eventName !== 'itemSelected') return
if (index === 0) this.onEdit()
else this.onRemove()
}
Source: https://cmichel.io/how-to-create-a-more-popup-menu-in-react-native

There is now a React Native plugin for this. I'm not sure it was around when the question was originally asked. But I'm leaving this here for anyone else looking for the answer.
https://www.npmjs.com/package/react-native-popup-menu
The example worked for me. I wanted to use the vertical ellipsis, so I did this modification to the MenuTrigger part of the example to an icon instead of text:
<MenuTrigger>
<Icon name="more-vert" size={25} color={colors.rustRed} />
</MenuTrigger>
As a side note, I had difficulty finding and using the ellipsis. I eventually went with using react-native-vector-icons by using 'npm -i react-native-vector-icons' and importing the Material Icons like this:
import Icon from 'react-native-vector-icons/MaterialIcons';

Use React Portals
https://reactjs.org/docs/portals.html
In short the receipts is:
You define your dynamic menu at sibling level only once in the parent i.e. in your case it would be adjacent to App.
Handle Click at each item level to open your component. You can pass some specific event days to achieve the dynamism.
Easier example https://codeburst.io/reacts-portals-in-3-minutes-9b2efb74e9a9
This achieves exactly what you are trying to do which is defer the creation of component untill clicked.

Related

How to change only the active button background color [React-Native]

import React, { Component } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
class App extends Component {
constructor(props){
super(props)
this.state={
selected:null
}
}
handle=()=>{
this.setState({selected:1})
}
render() {
return (
<View>
<TouchableOpacity style={[styles.Btn, {backgroundColor:this.state.selected===1?"green":"white"}]} onPress={this.handle}>
<Text style={styles.BtnText}>Button 1</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.Btn} onPress={this.handle}>
<Text style={styles.BtnText}>Button 2</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.Btn} onPress={this.handle}>
<Text style={styles.BtnText}>Button 3</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
Btn: {
borderWidth: 1,
width: 100,
height: 20,
borderRadius: 8,
margin: 5,
padding: 10,
justifyContent: 'center',
flexDirection: 'row',
alignItems: 'center',
},
BtnText: {
fontSize: 15,
},
});
export default App;
Snack Link : https://snack.expo.dev/U_fX-6Tao-
I want to make it so when I click a button, the active button backgroundColor should change to "green" and text to "white" and the rest of the buttons backgroundColor and textColor should stay "red". But when I click another button then that button should become active and the previous active button should get back to its normal state.
It would be wonderful if you could also explain the logic behind it as I'm a newbie in React Native.
Thank you.
You are always setting the active button to the first one. Also, I would use an array to render the buttons. I would do something like this:
class App extends Component {
constructor(props){
super(props)
this.state = {
selected: null
}
}
handlePress = (item) => {
this.setState({ selected: item })
}
render() {
return (
<View>
{[...Array(3).keys()].map((item) => {
return (
<TouchableOpacity key={item} style={[styles.Btn, {backgroundColor: this.state.selected === item ? "green" : "white" }]} onPress={() => this.handlePress(item)}>
<Text style={styles.BtnText}>Button {item + 1}</Text>
</TouchableOpacity>
)
})}
</View>
);
}
}
I created an Themed component(OK I did not create it. It is there when I create the app with Expo).
import { useState } from 'react';
import { TouchableOpacity as DefaultTouchableOpacity } from 'react-native';
export type TouchableProps = DefaultTouchableOpacity['props'] & { activeBgColor?: string };
export function TouchableOpacity(props: TouchableProps) {
const [active, setActive] = useState(false);
const { style, activeBgColor, ...otherProps } = props;
if (activeBgColor) {
return (
<DefaultTouchableOpacity
style={[style, active ? { backgroundColor: activeBgColor } : {}]}
activeOpacity={0.8}
onPressIn={() => setActive(true)}
onPressOut={() => setActive(false)}
{...otherProps}
/>
);
}
return <DefaultTouchableOpacity style={style} activeOpacity={0.8} {...otherProps} />;
}
Then I use this TouchableOpacity everywhere.
<TouchableOpacity
style={tw`rounded-sm h-10 px-2 justify-center items-center w-1/5 bg-sky-400`}
activeBgColor={tw.color('bg-sky-600')}
>
<Text style={tw`text-white font-bold`}>a Button</Text>
</TouchableOpacity>
Oh I am writing TailwindCSS with twrnc by the way. You will love it.
See the screenshot below.

How to use the modal in the list in react native (a specific Modal for each list item)?

I made a customized list component (in React Native) which shows touchable images with some description texts.
I need each images open a specific Modal; but I don't know how!! where & how I should code the Modal??
... here is my photo list component:
export class CustomGallery extends Component {
render() {
let {list} = this.props;
return (
<View style={styles.container}>
<FlatList
numColumns={4}
data={list}
renderItem={({ item}) => (
<View style={styles.views}>
<TouchableOpacity style={styles.touch} >
<ImageBackground
style={styles.img}
source={{ uri: item.photo }}
>
<Text style={styles.txt}>{item.name}</Text>
<Text style={styles.txt}>{item.key}</Text>
<Text style={styles.txt}>{item.describtion}</Text>
</ImageBackground>
</TouchableOpacity>
</View>
)}
/>
</View>
);
}
}
For Modal you can use modal from material-ui - https://material-ui.com/components/modal/
The Modal component renders its children node infront of a backdrop component. Simple and basic example would be like a confirmation message that pops up asking whether you surely want to delete particular information or not.
From your code I am guessing you want to display information regarding the image using modal when you click on the image.
Here I have added Modal component:
import React from 'react';
import Modal from '#material-ui/core/Modal';
export class CustomGallery extends Component {
constructor() {
super();
this.state = {
modalOpen: false,
snackOpen: false,
modalDeleteOpen: false,
};
}
handleModalOpen = () => {
this.setState({ modalOpen: true });
}
handleModalClose = () => {
this.setState({ modalOpen: false });
}
render() {
let {list} = this.props;
return (
<View style={styles.container}>
<FlatList
numColumns={4}
data={list}
renderItem={({ item}) => (
<View style={styles.views}>
<TouchableOpacity style={styles.touch} >
<ImageBackground
style={styles.img}
onClick={() => this.handleModalOpen()}
>
{ item.photo }
</ImageBackground>
<Modal
open={this.state.modalOpen}
onClose={this.handleModalClose}
closeAfterTransition
>
<Text style={styles.txt}>{item.name}</Text>
<Text style={styles.txt}>{item.key}</Text>
<Text style={styles.txt}>{item.describtion}</Text>
</Modal>
</TouchableOpacity>
</View>
)}
/>
</View>
);
}
}
I am not sure about how you set the image. But anyways below method is an example of opening modal with dynamic data.
import React, {useState} from "react";
import { Button, TouchableOpacity, FlatList, Modal, Text } from "react-native";
function App() {
const [value, setValue] = useState("");
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
},
];
return (
<>
<FlatList
data={DATA}
renderItem={({item}) => (
<TouchableOpacity onPress={() => setValue(item.title)}>
<Text>{item.title}</Text>
</TouchableOpacity>
)}
/>
<Modal visible={value}>
<Text>{value}</Text>
<Button title="close" onPress={() => setValue("")} />
</Modal>
</>
)
}
export default App;

How to stick the comment input box on top of keyboard [duplicate]

So I need to align a button Which is not at bottom om screen by design should be at middle of screen but it should align to be on top of the keyboard for all devices.
If you check this screenshot :
for Some devices I mange to do it, but in some others is not really aligned :
how can I manage this to work in all?
this is what I did so far :
<Padding paddingVertical={isKeyboardOpen ? Spacing.unit : Spacing.small}>
<Button
variant="solid"
label='Next'
style={styles.submitBtn}
/>
</Padding>
And isKeyboardOpen is just a method which will create a listner based on the platform return true if keyboard is open :
Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
true
);
And submitBrn css class is :
submitBtn: {
margin: Spacing.base,
},
First import this packages
import {
Button,
ScrollView,
KeyboardAvoidingView,
TextInput,
} from 'react-native';
Render method
<KeyboardAvoidingView
{...(Platform.OS === 'ios' ? { behavior: 'padding' } : {})}
style={styles.container}>
<ScrollView style={styles.scrollView}>
<TextInput style={styles.input} placeholder="Tap here" />
</ScrollView>
<Button title="Next" />
</KeyboardAvoidingView>
This are the styles
const styles = StyleSheet.create({
container: {
flex: 1,
},
scrollView: {
paddingHorizontal: 20,
},
input: {
marginBottom: 20,
borderBottomWidth: 2,
borderColor: '#dbdbdb',
padding: 10,
},
});
Make sure the button is outside the scrollview.
NOTE: You may need to adjust the offset prop of KeyboardAvoidingView if the keyboard has got autocomplete enabled.
Stick button at the bottom of the screen demo
In case anyone is still looking for a solution to this I am posting a working example from October 2021 along with the react native documentation. This example is for a text input that when focused has a button labeled 'Scanner' above the keyboard. React native documentation refers to what we are creating here as a 'toolbar'.
Please see this for further details https://reactnative.dev/docs/next/inputaccessoryview
import {
Button,
ScrollView,
TextInput,
StyleSheet,
InputAccessoryView,
Text,
View,
} from "react-native";
import { MaterialCommunityIcons } from "#expo/vector-icons";
import { BarCodeScanner } from "expo-barcode-scanner";
import { useFocusEffect } from "#react-navigation/native";
function CreateSearchBar() {
const inputAccessoryViewID = "uniqueID";
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const [showScanner, setShowScanner] = useState(false);
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
}, []);
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
setShowScanner(false);
};
}, [])
);
onChangeSearch = (search) => {};
setScannerShow = (show) => {
setShowScanner(show);
};
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
setShowScanner(false);
alert(`Bar code with type ${type} and data ${data} has been scanned!`);
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
if (showScanner) {
return (
<View style={styles.scanner}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
{scanned && (
<Button
title={"Tap to Scan Again"}
onPress={() => setScanned(false)}
/>
)}
</View>
);
}
return (
<>
<ScrollView keyboardDismissMode="interactive">
<View style={styles.input}>
<MaterialCommunityIcons name="magnify" size={24} color="black" />
<TextInput
onChangeText={onChangeSearch}
inputAccessoryViewID={inputAccessoryViewID}
placeholder="Find items or offers"
/>
<MaterialCommunityIcons
name="barcode"
size={30}
color="black"
onPress={() => setScannerShow(true)}
/>
</View>
</ScrollView>
<InputAccessoryView nativeID={inputAccessoryViewID}>
<Button onPress={() => setScannerShow(true)} title="Scanner" />
</InputAccessoryView>
</>
);
}
const styles = StyleSheet.create({
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
flexDirection: "row",
justifyContent: "space-between",
},
scanner: {
height: "50%",
},
});
export default CreateSearchBar;
YOu can use react native modal
<KeyboardAvoidingView
keyboardVerticalOffset={Platform.OS == "ios" ? 10 : 0}
behavior={Platform.OS == "ios" ? "padding" : "height"} style={{ flex: 1 }} >
<Modal>
<ScrollView>
<Content><-------Your content------></Content>
</ScrollView>
<BottomButton />
</Modal>
</KeyboardAvoidingView>

State does not change until the second button press? Adding a call back throws an error "invariant violation: invalid argument passed"

I'm trying to render a Flatlist that contains a store name. Upon clicking the store name, more information about the store should be displayed.
I attempted to change state and then use
{this.renderMoreDetails(item) && this.state.moreDetailsShown}
to get the additional information to appear. However, it was clear via console.log that state was only changed after a second button press.
From what I read in this article 1 and this article 2 it seemed as though I needed a callback function as an additional parameter to my setState().
I tried adding a callback function(probably incorrect, but I'm extremely lost at this point) and only got more errors.
I know that I have access to all of the data from renderItem inside FlatList. How do I make it appear upon click?
import React, { Component } from 'react';
import {
View,
FlatList,
Text,
TouchableWithoutFeedback,
ListView,
ScrollView
} from 'react-native'
import { NavigationActions } from 'react-navigation';
import { Header, Card, CardSection } from '../common';
import Config from '../Config';
export default class Costco extends Component<Props> {
constructor(props) {
super(props);
this.state = {
stores: [],
selectedItem: {id: null},
};
}
componentWillMount() {
const obj = Config.costcoThree;
const costcoArr = Object.values(obj);
this.setState({
stores: costcoArr,
})
}
renderListOfStores() {
return <FlatList
data={this.state.stores}
renderItem={ ({item}) => this.singleStore(item)}
keyExtractor={(item) => item.id}
extraData={this.state.selectedItem} />
}
singleStore(item) {
console.log(this.state.selectedItem.id)
return item.id === this.state.selectedItem.id ?
<TouchableWithoutFeedback
onPress={() => this.selectStore(item)}
>
<View>
<Text>{item.branchName}</Text>
<Text>Opening Time {item.openingTime}</Text>
<Text>Closing Time {item.closingTime}</Text>
<Text>Address {item.dongAddKor}</Text>
</View>
</TouchableWithoutFeedback>
:
<TouchableWithoutFeedback>
<View>
<Text>{item.branchName}</Text>
<Text>Only showing second part</Text>
</View>
</TouchableWithoutFeedback>
}
selectStore(item) {
this.setState({selectedItem: item});
console.log('repssed');
}
render() {
return(
<ScrollView>
<Header headerText="Costco"/>
<Text>hello</Text>
{this.renderListOfStores()}
</ScrollView>
)
}
}
as a workaround you can have a state that maintain your selected item to expand(or something like another view) and then you can use some conditions to work for render your flat list data.
Consider the below code and let me know if you need some more clarification.
import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,FlatList,TouchableNativeFeedback,
View
} from 'react-native';
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.state = {
response: [],
selectedItem: {id: null},
};
}
userListLayout() {
const {response} = this.state;
return <FlatList data={response} renderItem={ ({item}) => this.singleUserLayout(item)} keyExtractor={(item) => item.email} extraData={this.state.selectedItem} />
}
singleUserLayout(item) {
return item.id == this.state.selectedItem.id ?
<TouchableNativeFeedback onPress={() => this.userSelected(item)}>
<View style={{flex:1, borderColor: 'black', borderWidth: 1}}>
<Text style={{padding: 10, fontSize: 25}}>{item.email}</Text>
<Text style={{padding: 10,fontSize: 20}}>{item.name}</Text>
</View>
</TouchableNativeFeedback>
: <TouchableNativeFeedback onPress={() => this.userSelected(item)}><Text style={{padding: 10,fontSize: 20}}>{item.email}</Text></TouchableNativeFeedback>
}
userSelected(item) {
console.log(item)
this.setState({selectedItem: item})
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(data => data.json())
.then(response => this.setState({response}))
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>HI THERE</Text>
{this.userListLayout()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

How to show a drop-down menu on press of a button(TouchableHighlight) in react native

Picker(Native component of React-native) is different from drop-down menu, but I tried implementing it too , so please help me how to do that too .
press(){
return (
<Picker
selectedValue={this.state.language}
onValueChange={(lang) => this.setState({language: lang})}>
<Picker.Item label="Java" value="java" />
<Picker.Item label="JavaScript" value="js" />
</Picker>
);
}
tick(){
this.setState({picker: true});
}
var xyz= {this.state.picker} ? ({this.press}): (return(<View/>));
this is part of my render function , which contains an image button, by clicking that button I want to open a drop down menu .
<TouchableHighlight
underlayColor="gray"
onPress={this.tick}
style= {{flex:2,justifyContent:'center',alignItems:'center'}}>
<Image
style={{height:20,width:20,}}
source={require('./images/add-button.png')}/>
</TouchableHighlight>
{xyz}
I have set the default state of picker as false in Constructor.
Maybe react-native-dropdown is what you are looking for.
Usage:
var React = require('react-native');
var {
Component,
AppRegistry,
Text,
View,
} = React;
const DropDown = require('react-native-dropdown');
const {
Select,
Option,
OptionList,
updatePosition
} = DropDown;
class App extends Component {
constructor(props) {
super(props);
this.state = {
canada: ''
};
}
componentDidMount() {
updatePosition(this.refs['SELECT1']);
updatePosition(this.refs['OPTIONLIST']);
}
_getOptionList() {
return this.refs['OPTIONLIST'];
}
_canada(province) {
this.setState({
...this.state,
canada: province
});
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Select
width={250}
ref="SELECT1"
optionListRef={this._getOptionList.bind(this)}
defaultValue="Select a Province in Canada ..."
onSelect={this._canada.bind(this)}>
<Option>Java</Option>
<Option>Javascript</Option>
</Select>
<Text>Selected provicne of Canada: {this.state.canada}</Text>
<OptionList ref="OPTIONLIST"/>
</View>
);
}
}
AppRegistry.registerComponent('App', () => App);
Answers to questions:
refs is a way to reference the component you've added: More about refs
updatePosition accepts the reference and uses it to find where the dropdown should appear on the screen.