How can I close a Popover programatically with native base? - react-native

I am using Native Base Popover.
scenario
As a user,
I can press on the list,
So that I can select a fruit
My problem is I don't understand how to close the <Popover /> from the outside of the component.
Here is my organization
<Formik>
<Popover>
<FlatList>
<Pressable onPress={() => handlePress(item.id)} /> //Banaba
<Pressable onPress={() => handlePress(item.id)} /> //Potato
<Pressable onPress={() => handlePress(item.id)} /> //Ananas

NativeBase offers a useDisclose() hook for handling opening/closing of modals and other popup windows.
That hook provides an isOpen state (as #mainak's answer mentions) as well as onOpen() and onClose() functions to manipulate that state. You can pass these helpers as arguments as-needed into the props of the same name within the Popover component to handle open/close state.
Optionally, you can in addition pass true or false into useDisclose() to override the starting value of isOpen (defaults to false).
Here is an example below for reference.
import React from "react";
import { Popover, useDisclose } from "native-base";
function MyComponent() {
const { isOpen, onClose, onOpen } = useDisclose()
return (
<>
<Button onPress={onOpen}>Open the Popover</Button>
<Popover isOpen={isOpen} onClose={onClose}>
<Popover.Content>
<Popover.Arrow />
<Popover.CloseButton />
<Popover.Header>My Popover Title</Popover.Header>
<Popover.Body>You can place the content of your popover inside the body.</Popover.Body>
<Popover.Footer>
<Button onPress={onClose} variant="ghost">Cancel</Button>
</Popover.Footer>
</Popover.Content>
</Popover>
</>
)
}

can you try isOpen prop in Popover tag and have it as a state value like
const [isOpen, setOpen] = React.useState(true);
...
<Formik>
<Popover isOpen={isOpen}>
<FlatList>
...

Related

How to Add Awesome Alert in Functional Component

I'm using Awesome Alert for customize alert but I would like to apply it in functional component environment. This is my design
And this is my code
<AwesomeAlert
show={true}
showProgress={false}
title="AwesomeAlert"
message="I have a message for you!"
closeOnTouchOutside={false}
closeOnHardwareBackPress={false}
showCancelButton={true}
showConfirmButton={true}
cancelText="Reject"
confirmText="Approve"
confirmButtonColor="#AEDEF4"
cancelButtonColor="#DD6B55"
onCancelPressed={() => {
console.log("Reject")
}}
onConfirmPressed={() => {
console.log("Approve")
}}
/>
My question is, is Awesome Alert can only be applied in Class component? How am I able to implement it in Functional Component like the original Alert React Native?
You can use AwesomeAlert inside a functional component as you would inside a class component's render() function; any React component that can be rendered from a class component can also be rendered from a functional component.
Just put it inside your return part of your functional component and it will work
For Example like this:
return (
<View>
<AwesomeAlert
show={true}
showProgress={false}
title="AwesomeAlert"
message="I have a message for you!"
closeOnTouchOutside={false}
closeOnHardwareBackPress={false}
showCancelButton={true}
showConfirmButton={true}
cancelText="Reject"
confirmText="Approve"
confirmButtonColor="#AEDEF4"
cancelButtonColor="#DD6B55"
onCancelPressed={() => {
console.log("Reject")
}}
onConfirmPressed={() => {
console.log("Approve")
}}
/>
</View>
)

react-native-snap-carousel is very laggy for large data

I am using react-native-snap-carousel to swipe through images. When there is like 0-10 images it's working fine, but otherwise it's very laggy. I tried the optimization methods but didn't fix it.
Here is my implementation (selectedItems is the data I have):
const renderItem = useCallback(
({ item, index }) => {
return (
<CarouselImage
ad={ad}
item={item}
index={index}
showImage={showImage}
/>
);
},
[ad, showImage]);
return ad?.videos?.length > 0 || ad?.images?.length > 0 ? (
<View style={styles.container}>
<Carousel
initialNumToRender={selectedItems.length}
maxToRenderPerBatch={5}
ref={carouselRef}
swipeThreshold={5}
itemWidth={wp(375)}
data={selectedItems}
sliderWidth={wp(375)}
enableMomentum={false}
lockScrollWhileSnapping
renderItem={renderItem}
onSnapToItem={(index) => setActiveSlide(index)}
/>
<Pagination
activeOpacity={1}
tappableDots={true}
animatedDuration={100}
inactiveDotScale={0.4}
inactiveDotOpacity={0.4}
carouselRef={carouselRef}
dotStyle={styles.dotStyle}
activeDotIndex={activeSlide}
dotsLength={selectedItems.length}
containerStyle={styles.pagination}
dotContainerStyle={styles.dotContainer}
inactiveDotStyle={styles.inactiveDotStyle}
/>
</View>
Is there something I am missing. Also, is there an alternative library that runs better with large data ?
Try this alternative library: react-native-banner-carousel-updated
I'm using this with more than 20 images and it's works fine.
I'm assuming that the optimization you've tried is what is described in the react-native-snap-carousel library docs...
I too found that every swipe was causing my screen's component to re-render.
You might be thinking...
I think it has to do with the state I am updating each time I scroll the component is re-rendered. Do you have ant idea how to solve this ?
To prevent the re-rendering of your <Carousel ... /> component, the optimization that you want to look at is to utilize React.memo()
Try refactoring your <Carousel ... /> component to a new component file, something like this...
Gallery.js
import React from "react";
import CarouselCardItem, { SLIDER_WIDTH } from "./CarouselCardItem";
import { ALL_RECIPES } from "../config/Recipe/allRecipes";
import Carousel from "react-native-snap-carousel";
const Gallery = ({ carouselRef, selectedItems, setActiveSlide}) => {
console.log("render"); // <== This will render only when props change (ie. the ref - which should not change, or you pass fresh data - in which case you want it to re-render)
return (
<Carousel
initialNumToRender={selectedItems.length}
maxToRenderPerBatch={5}
ref={carouselRef}
swipeThreshold={5}
itemWidth={wp(375)}
data={selectedItems} // <== selectedItems could be passed in as prop, or from app state
sliderWidth={wp(375)}
enableMomentum={false}
lockScrollWhileSnapping
renderItem={renderItem}
onSnapToItem={(index) => setActiveSlide(index)}
/>
);
};
const GalleryMemo = React.memo(Gallery);
export default GalleryMemo;
Then in your screen file, use it
<GalleryMemo carouselRef={carouselRef} selectedItems={selectedItems} setActiveSlide={setActiveSlide}/>

How do I bind a local function when I pass a rendered view interacting with that function to a prop in react-native?

I'm passing a render to the Accordion element in native-base using the renderContent prop. The render contains two buttons, which, when pressed, run functions that are local to the current component. Unfortunately those functions are not available once it has been actually rendered.
How do I bind the functions properly so that when pressed, the correct functions are referenced?
I'm using the most modern stable releases of react-native, native-base, and I'm running this through expo for testing.
Here's the documentation on native-base:
http://docs.nativebase.io/Components.html#accordion-custom-header-content-headref
Accordion:
<Accordion
dataArray={ this.state.websites }
renderContent={ this._renderAccordionContent }
/>
renderContent:
_renderAccordionContent(content) {
return (
<Button
onPress={() => this.openSite(content.path)}
>
<Text>Open</Text>
</Button>
<Button
onPress={() => this.editSite(content.key)}
>
<Text>Edit</Text>
</Button>
)
}
When the buttons are pressed, the expected results are that the functions are run.
The actual results are that when the buttons are pressed, these errors are populated:
_this2.openSite is not a function.
_this2.editSite is not a function.
Thank you for any help.
Check out this excellent article that shows several different ways of binding your functions https://medium.freecodecamp.org/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56
Here is an example of binding it in the constructor of your component that uses the Accordion component. It is by no means the only way of binding the functions. The above article gives 5 different ways of doing it.
class MyComponent extends Component {
constructor(props) {
super(props);
this.openSite = this.openSite.bind(this);
this.editSite = this.editSite.bind(this);
}
// I am assuming you have written your functions like this and not as arrow functions
openSite (path) {
...
}
editSite (key) {
...
}
_renderAccordionContent(content) {
return (
<Button
onPress={() => this.openSite(content.path)}
>
<Text>Open</Text>
</Button>
<Button
onPress={() => this.editSite(content.key)}
>
<Text>Edit</Text>
</Button>
)
}
render() {
...
<Accordion
dataArray={ this.state.websites }
renderContent={ this._renderAccordionContent }
/>
...
}
}

Opening context menu on long press in React Native

I'd like to have a context menu triggered on long press different places using React Native.
I.e. in a dialer like the default dailer. You can long-click on any contact and get a 'copy number' menu. And also you can long-click on the name of the person once you've opened their 'contact card'.
The straight-forward way needs a lot of copy-pasted boilerplate, both components and handlers.
Is there a better pattern for doing this?
All Touchable components (TouchableWithoutFeedback, TouchableOpacity etc.) has a property called onLongPress. You can use this prop to listen for long presses and then show the context menu.
To eliminate code mess and doing lots of copy paste you can separate your context menu as a different component and call it when the long press happen. You can also use an ActionSheet library to show the desired options. React native has a native API for iOS called ActionSheetIOS. If you get a little bit more experience in react and react-native you can create a better logic for this but I'm going to try to give you an example below.
// file/that/contains/globally/used/functions.js
const openContextMenu = (event, user, callback) => {
ActionSheetIOS.showActionSheetWithOptions({
options: ['Copy Username', 'Call User', 'Add to favorites', 'Cancel'],
cancelButtonIndex: [3],
title: 'Hey',
message : 'What do you want to do now?'
}, (buttonIndexThatSelected) => {
// Do something with result
if(callback && typeof callback === 'function') callback();
});
};
export openContextMenu;
import { openContextMenu } from './file/that/contains/globally/used/functions';
export default class UserCard extends React.Component {
render() {
const { userObject } = this.props;
return(
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done')}>
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done'))}>
<Text>{userObject.name}</Text>
<Image source={{uri: userObject.profilePic }} />
</TouchableWithoutFeedback>
</TouchableWithoutFeedback>
);
}
}
Similarly as the previous answer combine onLongPress with imperative control for popup menu - something like
<TouchableWithoutFeedback onLongPress={()=>this.menu.open()}>
<View style={styles.card}>
<Text>My first contact name</Text>
<Menu ref={c => (this.menu = c)}>
<MenuTrigger text="..." />
<MenuOptions>
// ...
</MenuOptions>
</Menu>
</View>
</TouchableWithoutFeedback>
When it comes to a lot of boilerplate - in React you can do your own components that you can reuse everywhere thus reducing boilerplate (and copy&paste)
See full example on https://snack.expo.io/rJ5LBM-TZ

React Native/Shoutem: navigateBack() not working

My Problem is that I would like to navigateBack() from the BountyDetailsScreen to the LoyaltyScreen, but the navigateBack() function call does not trigger any action. When I log the function it says:
The only thing I notice is, that the navigationStack is empty. When I do the same with the navigateTo function it is working, but then I have a messed up navigation stack.
In my LoyaltyScreen.js I am displaying a ListView. It is a RN ListView (not imported from shoutem).
LoyaltyScreen.js
renderRow(bounty) {
return (
<ListBountiesView
key={bounty.id}
bounty={bounty}
onDetailPress={this.openDetailsScreen}
redeemBounty={this.redeemBounty}
/>
);
}
ListBountiesView.js
The ListBountiesView renders each ListView Row and opens a Detail Screen when clicked on the Row.
render() {
const { bounty } = this.props;
return (
<TouchableOpacity onPress={this.onDetailPress}>
{bounty.type == 0 ? this.renderInShopBounty() : this.renderContestBounty()}
<Divider styleName="line" />
</TouchableOpacity>
);
}
BountyDetailsScreen.js
In the BountyDetailsScreen I display detailed information and would like to navigateBack() to the Loyalty Screen when I press a button.
<Button styleName="full-width" onPress={() => this.onRedeemClick()}>
<Icon name="add-to-cart" />
<Text>Einlösen</Text>
</Button>
onRedeemClick() {
const { bounty, onRedeemPress } = this.props;
onRedeemPress(bounty);
navigateBack();
}
navigateBack is an action creator. You need to map it to props and read it from props in your redeemClick function. Just executing the imported action creator won't do anything since it's not connected to Redux.
Here's an example of you map it to props:
export default connect(mapStateToProps, { navigateBack })(SomeScreen));
Here's how you use it:
const { navigateBack } = this.props;
navigateBack();
I can see that airmiha's answer is what you're looking for, but I just wanted to add onto it.
You can also use hasHistory to set up your #shoutem/ui NavigationBar (if you're using it) with a simple back button that utilises navigateBack().
<NavigationBar
styleName="no-border"
hasHistory
title="The Orange Tabbies"
share={{
link: 'http://the-orange-tabbies.org',
text: 'I was underwhelmed by The Orange Tabbies, but then I looked at that
sweet, sweet back button on the Nav Bar.
#MakeNavBarsGreatAgain',
title: 'Nevermind the cats, check the Nav Bar!',
}}
/>
You can find more examples with the NavigationBar component here.