How do I bind a local function when I pass a rendered view interacting with that function to a prop in react-native? - 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 }
/>
...
}
}

Related

How can I close a Popover programatically with native base?

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>
...

React-native Formik setFieldValue

Here is a simplified version of my code.
Notice the setFieldValue_ and this.setFieldValue_ = setFieldValue;
This code works fine, I'm able to get the output when submit button is clicked.
I'm actually wondering if this is the right way to do it? If not, can you point me to the right direction? Also what is this method called? (assigning class variable to some function and use it within another function)
class MyComponent extends React.Component {
setFieldValue_;
someFunction() {
this.setFieldValue_("name", value);
}
render() {
return (
<Formik
initialValues={{
something: ""
}}
onSubmit={values => console.log(values)}
>
{({
setFieldValue,
}) => {
this.setFieldValue_ = setFieldValue;
<ThirdPartyCustomComponent onChange={this.someFunction} />
}}
</Formik>
}
}
I would personally have the onChange simply call formik set field value there and then rather than using different functions. Strictly speaking you don't want to set the value like that because every re-render is setting the value again.
I would also recommend looking at custom formik inputs using the useField hook - https://jaredpalmer.com/formik/docs/api/useField. This will allow you to write a small wrapper around your third party component and formik. Noticing you have used a class based component you may want to do some short reading into react hooks before throwing yourself into using useField.
Docs example:
const MyTextField = ({ label, ...props }) => {
const [field, meta, helpers] = useField(props);
return (
<>
<label>
{label}
<input {...field} {...props} />
</label>
{meta.touched && meta.error ? (
<div className='error'>{meta.error}</div>
) : null}
</>
);
};

React Native components seem to be sharing a state

I'm having an issue with React-native where I have a component TouchTimer which uses an AnimatedTimer component. This timer is supposed to start and stop when it is tapped, which it does, however all of the TouchTimer components I add to a page will start and stop whenever any of them are tapped, rather than only affecting the tapped component.
Below is a snippet of my component:
TouchTimer.tsx
export class TouchTimer extends React.Component<TouchTimerProps> {
state: {
...
paused: boolean,
}
constructor(props) {
super(props);
...
this.state = {
...
paused: true,
}
}
startStop() {
this.setState({paused: !this.state.paused});
}
render() {
const { time } = this.props;
return (
<TouchableHighlight onPress={() => this.startStop()}>
<View>
<AnimatedTimer
...
time={time}
pause={this.state.paused}
/>
<View style={styles.timeContainer}>
<Text style={styles.time}>{this.state.remaining}</Text>
</View>
</View>
</TouchableHighlight>
)
}
}
And here is a snippet of the screen containing these components:
Details.tsx
import { TouchTimer } from '../components/TouchTimer';
...
export class RecipeDetailsScreen extends React.Component<NavigationInjectedProps> {
...
{this.state.steps.map(step => (
<List.Item
key={step.id}
title={"Step " + step.index}
style={styles.step}
description={step.short_desc}
right={() => (step.time > 0 &&
<TouchTimer
time={step.time * 60000}
/>
)}
/>
)
}
I have tried wrapping the TouchTimer components in a View and changing the paused boolean to a prop, to no avail.
I have also tested to see if this issue appears when the components are not siblings, and when they are not produced as the result of a callback, and the issue still persists in both these cases.
If anybody has any advice or answers on how to make these timers independent I would very much appreciate it!
Curiously that component seems to be implemented with a global pauseFlag that applies to all component instances. See https://github.com/dalisalvador/react-native-animated-timer/blob/master/src/Components/AnimatedTimer.js#L34
So I don't think you're doing anything wrong here, this is a limitation of the library code that is coupling all instances of your timer to the same pauseFlag value.

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.

React Native passing functions with arguments as props

From what I have read its best to try and structure react apps with as many components as "dumb" renderers. You have your containers which fetch the data and pass it down to the components as props.
That works nicely until you want to pass functions down the chain that require arguments other than events.
class MyClass extends Component {
_onItemPress (myId) {
// do something using myId
}
render () {
return <MyComponent myID={10} onPress={(myId) => this._onItemPress(myId)} />
}
}
If I simply pass that as my onPress handler to MyComponent it won't return myId when called. To get around this I end up doing something like this.
export default ({myId, onPress) => {
const pressProxy = () => {
onPress(myId)
}
return (
<TouchableHighlight onPress={pressProxy}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
Am I doing this completely incorrectly? I would like to be able to have a simple component that I can re-use for list items where its sole function is to take a title, onpress function and return a list item to be used in ListViews renderRow function. Many of the onPress functions will require variables to be used in the onPress however.
Is there a better way?
The proper syntax would be something like this:
render () {
let myId = 10;
return <MyComponent myID={myId} onPress={() => this._onItemPress(myId)} />
}
Also, if you plan to use this inside _onItemPress (for example to call other methods in MyClass), you need to bind the scope like this:
render () {
let myId = 10;
return <MyComponent
myID={myId}
onPress={() => this._onItemPress(myId).bind(this)} />
}
...or you can bind it already in the constructor if you prefer:
constructor(props) {
super(props);
this._onItemPress = this._onItemPress.bind(this);
}
You did it correctly.
MyComponent is as "dumb" as it should be: it does not care about the source of its props, it acts independently from higher level of logic of the app and it can be reused somewhere else in the app.
Some improvements you can work on:
MyComponent does not need myId itself. Exclude it from the component and let parental component deals with related logics to id
Provide a safe check for props onPress. If you want to reuse MyComponent somewhere, it is better to check for existence of onPress property before calling it, or provide a default value for onPress in case developer adds in unwanted props types.
Example of MyComponent
class MyComponent extends Component {
handlePress = (e) => {
if (typeof this.props.onPress === 'function') {
this.props.onPress()
}
}
render() {
return (
<TouchableHighlight onPress={this.handlePress}>
<Text>Click me to trigger function</Text>
</TouchableHighlight>
)
}
}
and to call MyComponent in MyClass:
class MyClass extends Component {
_onItemPress(myId) {
}
render () {
return <MyComponent onPress={() => this._onItemPress(10)} />
}
}