How do I test a function inside a component by using jest? - javascript

I have a unit testing coverage report for a component MyCheckbox.js.
coverage
How do I test the onCheckmarkPress() function in MyCheckbox.js?
Here is the implementation of MyCheckbox.js:
import * as React from 'react';
import { Pressable, StyleSheet } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
import { useState } from 'react';
/**
*
* #param {() => void} props.onUpdate called when checkbox is pressed
* #return {JSX.Element}
* #constructor
*/
const MyCheckbox = (props) => {
const [checked, onChange] = useState(false);
function onCheckmarkPress() {
onChange((prev) => {
let checked = !prev;
props.onUpdate(checked);
return checked;
});
}
return (
<Pressable
style={[styles.checkboxBase, checked && styles.checkboxChecked]}
onPress={onCheckmarkPress}
>
{checked && <Ionicons name="checkmark" size={24} color="black" />}
</Pressable>
);
};
const styles = StyleSheet.create({
checkboxBase: {
width: 35,
height: 35,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 4,
borderWidth: 2,
borderColor: 'white',
backgroundColor: 'white',
},
checkboxChecked: {
backgroundColor: '#C4C4C4',
},
});
export default MyCheckbox;
This is how I attempt the test case:
import React from 'react'
import MyCheckbox from '../MyCheckbox';
import {fireEvent, render, screen} from "#testing-library/react-native";
import '#testing-library/jest-dom';
it("works", () => {
const onUpdateMock = jest.fn();
render(<MyCheckbox onUpdate={onUpdateMock} />);
expect(onUpdateMock).toHaveBeenCalledTimes(0);
expect(screen.queryByTestId("checkIcon")).toBeNull();
const pressable = screen.getByRole("pressable");
fireEvent.press(pressable);
expect(onUpdateMock).toHaveBeenCalledTimes(1);
expect(screen.queryByTestId("checkIcon")).toBeInTheDocument(); // check that the icon is rendered
});
However, I am getting the error "Unable to find an element with accessibilityRole: pressable". And will this cover the red lines marked in the coverage report?

You should test the behaviour of your component, so it would look sth like this:
Render the component
Find the Pressable component
Make sure that check icon is not displayed
emulate the click event on Pressable component or other event (touch?) it responds to
Check if check icon is displayed
This kind of test will give you the coverage and the functionality tested.
Please provide the Pressable source if you need more detailed answer, it is hard to tell if it is button or some other implementation.
You can use the React Testing Library to achieve all of above steps.
Assuming your MyCheckbox works like this:
const MyCheckbox = (props) => {
const [checked, onChange] = React.useState(false);
const onCheckmarkPress = () => {
onChange((prev) => {
let checked = !prev;
props.onUpdate(checked);
return checked;
})
}
return (
<button onClick={onCheckmarkPress}>
{checked && <IonIcon data-testid="checkIcon" name="checkmark" />}
</button>
);
};
You could test it like this:
import { fireEvent, render, screen } from "#testing-library/react";
import MyCheckBox from "../MyCheckbox";
it("should work", () => {
const onUpdateMock = jest.fn();
render(<MyCheckBox onUpdate={onUpdateMock} />);
expect(screen.queryByTestId("checkIcon")).not.toBeInTheDocument(); // check that the icon is not rendered
const btn = screen.getByRole("button"); // get the button (pressable)
fireEvent.click(btn); // click it
expect(screen.getByTestId("checkIcon")).toBeInTheDocument(); // check that the icon is displayed
expect(onUpdateMock).toHaveBeenCalledTimes(1); // make sure that the onUpdate function that was passed via props was clicked
});

Related

Mock antd useBreakpoint hook

I want to test the modal component, but there is an error with defining the cancel button,
it renders only if it's not mobile.
isMobile is a variable that is a boolean value from hook - useBreakpoint (ant design library hook).
I don't know how to mock that value, or how to click that button.
Note: if I remove the isMobile check, the button clicks well:)
import React from 'react'
import {Grid, Typography} from 'antd'
import {Button} from '#/components/Button'
import {Modal} from '#/components/Modal'
import translations from './translations'
import {ConfirmationModalProps} from './props'
const {Text} = Typography
const {useBreakpoint} = Grid
export const ConfirmationModal = ({visible, onClose, children}: ConfirmationModalProps) => {
const screens = useBreakpoint()
const isMobile = screens.xs
return (
<Modal
title={translations().chargeConfirmation}
visible={visible}
onOk={onClose}
onCancel={onClose}
footer={[
!isMobile && (
<Button role={'cancel-button'} type={'ghost'} key={'cancel'} onClick={onClose}>
{ translations().cancel }
</Button>
),
<Button type={'primary'} key={'charge'} onClick={onClose}>
{ translations().confirm }
</Button>
]}
>
<Text>{translations().confirmationText(children)}</Text>
</Modal>
)
}
describe('ConfirmationModal', () => {
it('should should the children and close button', async () => {
const onClose = jest.fn()
jest.mock('antd/es/grid/hooks/useBreakpoint', () => ({
xs: false
}))
render(<ConfirmationModal onClose={onClose} visible={true}>100</ConfirmationModal>)
const child = screen.getByText('Are you sure you want to charge 100')
expect(child).toBeTruthy()
expect(screen.queryByTestId('cancel')).toBeDefined()
await waitFor(() => screen.queryByTestId('cancel'))
fireEvent.click(screen.queryByRole('cancel-button'))
expect(onClose).toHaveBeenCalledTimes(1)
})
})
Errors are:
Error: Unable to fire a "click" event - please provide a DOM element.
Unable to find an accessible element with the role "cancel-button"
Depending on queryByRole or getByRole selector.
What is wrong?
Let's take a look at the source code of the useBreakpoint hook.
import { useEffect, useRef } from 'react';
import useForceUpdate from '../../_util/hooks/useForceUpdate';
import type { ScreenMap } from '../../_util/responsiveObserve';
import ResponsiveObserve from '../../_util/responsiveObserve';
function useBreakpoint(refreshOnChange: boolean = true): ScreenMap {
const screensRef = useRef<ScreenMap>({});
const forceUpdate = useForceUpdate();
useEffect(() => {
const token = ResponsiveObserve.subscribe(supportScreens => {
screensRef.current = supportScreens;
if (refreshOnChange) {
forceUpdate();
}
});
return () => ResponsiveObserve.unsubscribe(token);
}, []);
return screensRef.current;
}
export default useBreakpoint;
It uses ResponsiveObserve.subscribe() to get the supportScreens, it calls ResponsiveObserve.register(), the .register() method use window.matchMedia() underly. jestjs use JSDOM(a DOM implementation) as its test environment, but JSDOM does not implement window.matchMedia() yet. So we need to mock it, see Mocking methods which are not implemented in JSDOM
E.g.
import { render } from '#testing-library/react';
import React from 'react';
import { Grid } from 'antd';
const { useBreakpoint } = Grid;
describe('72021761', () => {
test('should pass', () => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(
(query) =>
({
addListener: (cb: (e: { matches: boolean }) => void) => {
cb({ matches: query === '(max-width: 575px)' });
},
removeListener: jest.fn(),
matches: query === '(max-width: 575px)',
} as any)
),
});
let screensVar;
function Demo() {
const screens = useBreakpoint();
screensVar = screens;
return <div />;
}
render(<Demo />);
expect(screensVar).toEqual({
xs: true,
sm: false,
md: false,
lg: false,
xl: false,
xxl: false,
});
});
});

React Native: undefined is not an object (evaluating 'useContext.getItemsCount')

I'm a beginner on React Native and I am getting this error when getItemsCount is called.
*Please Click on the Links to see images
https://i.stack.imgur.com/wbwjZ.png
This is the code for CartIcon.js:
import React, {useContext} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {CartContext} from './CartContext';
export function CartIcon({navigation}){
const {getItemsCount} = useContext(CartContext);
return(
<View style = {styles.container}>
<Text style = {styles.text}
onPress = {() => {
navigation.navigate('Cart');
}}
>Cart ({getItemsCount()}) </Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 10,
backgroundColor: '#515b8c',
height: 40,
padding: 15,
borderRadius: 38/2,
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: '#ccc',
fontWeight: 'normal',
},
});
https://i.stack.imgur.com/ABYHm.png
This is the code for CartContext.js:
import React, {createContext, useState} from 'react';
import {getProduct} from './productService.js';
export const CartContext = createContext();
export function CartProvider(props){
const [items, setItems] = useState([]);
function addItemToCart(id){
const product = getProduct(id);
setItems((prevItems) => {
const item = prevItems.find((item) => (item.id == id));
if(!item){
return [...prevItems, {
id,
qty: 1,
product,
totalPrice: product.price
}];
}
else{
return prevItems.map((item) => {
if(item.id == id){
item.qty++;
item.totalPrice += product.price;
}
return item;
});
}
});
}
function getItemsCount(){
return items.reduce((sum,item) => (sum+item.qty),0);
}
function getTotalPrice(){
return items.reduce((sum,item) => (sum+item.totalPrice),0);
}
return(
<CartContext.Provider
value = {{items,setItems,getItemsCount,addItemToCart,getTotalPrice}}>
{props.children}
</CartContext.Provider>
);
}
https://i.stack.imgur.com/HsXoY.png
Taking a guess, but I would think that your component is outside of the provider, please check that your CartIcon is actually inside of the CartContext.Provider, otherwise it won't have access to it.
please add this to CartContext.js:
const useCartContext = () => {
const context = useContext(CartContext);
if (context === undefined) {
throw new Error('useCartContext must be used within a CartContextProvider');
}
return context;
};
and
export { CartProvider, useCartContext };
Go to App.jsx and wrap the whole app with
<CartProvider>
// your app
</CartProvider>
Then in CartIcon.js import useCartContext and replace
const {getItemsCount} = useContext(CartContext);
with
const { getItemsCount } = useCartContext();
Let me know what happens. The idea is to create a hook, which is nicer, but the issue here is that your component needs to be inside a provider for it to have access to the context.
The Reason for MySide getting Error is forget to use return statement while creating Global context .
Check that Side also .
** -> 1 more Side can be not Using ContextProvider or wrapping in the App.jsx File App.js File ( Mostly people Forget ).

How do I send a function parameter to AsyncStorage?

I want to send the parameter to the function submitLanguageSelection, which is userSelectedLanguage, to a custom hook I've written which (hopefully) saves that parameter to AsyncStorage. The user selects a language, either English or Arabic, from one of the two buttons.
This is my first time ever doing this. I've gotten very stuck.
I would like the submitLanguageSelection function to call the saveData function which is made available through the useLocalStorage hook. I would like the user's choice of language to be persisted in AsyncStorage so I can then later render the ChooseYourLanguageScreen according to whether the user has selected a language or not.
Here is the cutom hook, useLocalStorage:
import React from 'react';
import { Alert } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage';
const STORAGE_KEY = '#has_stored_value';
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async () => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, storedValue);
if (localValue !== null) {
setStoredValue(storedValue);
Alert.alert('Data successfully saved');
}
console.log('stored val', storedValue);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};
Here is the ChooseYourLanguageScreen:
import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import useLocalStorage from '../hooks/useLocalStorage';
const ChooseYourLanguageScreen = ({ navigation }) => {
const [saveData, errorMessage] = useLocalStorage();
const submitLanguageSelection = (userSelectedLanguage) => {
//TODO: save the data locally
//TODO: navigate to welcome screen
// at the moment, the language choice isn't making it to useLocalStorage
if (userSelectedLanguage !== null) {
console.log('user selected lang', userSelectedLanguage);
saveData(userSelectedLanguage);
}
};
return (
<View style={styles.container}>
{errorMessage ? <Text>{errorMessage}</Text> : null}
<Text style={styles.text}>This is the Choose Your Language Screen</Text>
<View style={styles.buttons}>
<View>
<Button
title={'English'}
onPress={() => submitLanguageSelection('English')}
/>
</View>
<View>
<Button
title={'Arabic'}
onPress={() => submitLanguageSelection('Arabic')}
/>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
alignSelf: 'center',
},
buttons: {
backgroundColor: '#DDDDDD',
padding: 10,
},
});
export default ChooseYourLanguageScreen;
saveData() needs a parameter. You can provide a default value that uses storedValue that came from React.useState(), but when you call it with an explicit argument it will override that default.
export default () => {
const [storedValue, setStoredValue] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const saveData = async (dataToSave = storedValue) => {
try {
const localValue = await AsyncStorage.setItem(STORAGE_KEY, dataToSave);
if (localValue !== null) {
setStoredValue(dataToSave);
Alert.alert('Data successfully saved');
}
console.log('stored val', dataToSave);
} catch (e) {
setErrorMessage('Something went wrong');
}
};
return [saveData, errorMessage];
};

React Native Hooks: unable to display values in return() from useEffect function

Using react hooks with firebase real time database with a project Im working on for the first time, the data is being retrieved and logs in the console. BUT I am unable go move out side the useEffect function to display values on the screen! Would really appreciate some help!
videoArray needs to be put in a FlatList, as you can see in the code bellow videoArray logs values in the console in useEffect function. However, once I move out that function to add it into a FlatList it is null because the values are in a local function.
My question is, how am I able to pass value of videoArray (in useEffect) into the FlatList?
import React, { useState, useEffect } from 'react'
import { FlatList, View, TouchableOpacity, Text, StyleSheet, SafeAreaView } from 'react-native';
import { Center } from '../components/Center'
import { Video } from 'expo-av';
import firebase from '../firebase'
const videoRef = firebase.database().ref('videoCollaction');
export const FeedScreen = ({ }) => {
let [videoArray, setVideo] = useState([]);
useEffect(() => {
videoRef.on('value', (childSnapshot) => {
videoArray = [];
childSnapshot.forEach((doc) => {
videoArray.push({
key: doc.key,
video: doc.toJSON().video,
});
})
})
// able to log values only here
console.log('working:', videoArray);
});
// get video uri from firebase (testing)
// readVideo = () => {
// var collection = firebase.database().ref("videoCollactionvideo" + "/video").orderByValue();
// console.log('uri', collection);
// }
return (
<SafeAreaView>
<Text>Feed Screen</Text>
{/* null here: need values to show up here*/}
{console.log(" test",videoArray)}
<FlatList
data={videoArray}
renderItem={({ item, index }) => {
return (
<View>
<Text style={{ fontSize: 35, color: 'red' }}>Video:...</Text>
<TouchableOpacity onPress={() => console.log('pressed')}><Text style={{ color: 'blue' }}>Expand</Text></TouchableOpacity>
</View>
);
}} keyExtractor={({ item }, index) => index.toString()}>
</FlatList>
</SafeAreaView>
);
}
Try this:
useEffect(() => {
const temp = []; // temp array
videoRef.on("value", childSnapshot => {
childSnapshot.forEach(doc => {
temp.push({
key: doc.key,
video: doc.toJSON().video
});
});
setVideo(temp); // update state array
});
}, []);
It seems like you are trying to update the State Hook (videoArray), but you are doing it the wrong way (it shouldn't be modified directly). Instead, use the setVideo update method which you created with the Hook (let [videoArray, setVideo] = useState([]);):
useEffect(() => {
videoRef.on('value', (childSnapshot) => {
newVideoArray = [];
childSnapshot.forEach((doc) => {
newVideoArray.push({
key: doc.key,
video: doc.toJSON().video,
});
})
})
// able to log values only here
console.log('working:', newVideoArray);
setVideo(newVideoArray);
});
Check out Using the Effect Hook for more information on how to use this specific hook (the Optimizing Performance by Skipping Effects section might be especially of interest).
In essence, this functionality is similar to your Functional Component's stateful counterparts (React.Component or React.PureComponent), where:
Constructor is the only place where you should assign this.state directly. In all other methods, you need to use this.setState() instead.
Try this:
import React, { useState, useEffect } from 'react'
import { FlatList, View, TouchableOpacity, Text, StyleSheet, SafeAreaView } from 'react-native';
import { Center } from '../components/Center'
import { Video } from 'expo-av';
import firebase from '../firebase'
const videoRef = firebase.database().ref('videoCollaction');
export const FeedScreen = ({ }) => {
let [videoArray, setVideoArray] = useState([]);
useEffect(() => {
videoRef.on('value', (childSnapshot) => {
const newVideoArray = [];
childSnapshot.forEach((doc) => {
newVideoArray.push({
key: doc.key,
video: doc.toJSON().video,
});
})
setVideoArray(newVideoArray);
})
// able to log values only here
console.log('working:', videoArray);
}, []);
console.log('State also working :) >> ', videoArray);
return (
<SafeAreaView>
<Text>Feed Screen</Text>
{/* null here: need values to show up here*/}
{console.log(" test",videoArray)}
<FlatList
data={videoArray}
renderItem={({ item, index }) => {
return (
<View>
<Text style={{ fontSize: 35, color: 'red' }}>Video:...</Text>
<TouchableOpacity onPress={() => console.log('pressed')}><Text style={{ color: 'blue' }}>Expand</Text></TouchableOpacity>
</View>
);
}} keyExtractor={({ item }, index) => index.toString()}>
</FlatList>
</SafeAreaView>
);
}

Prevent Double tap in React native

How to prevent a user from tapping a button twice in React native?
i.e. A user must not be able tap twice quickly on a touchable highlight
https://snack.expo.io/#patwoz/withpreventdoubleclick
Use this HOC to extend the touchable components like TouchableHighlight, Button ...
import debounce from 'lodash.debounce'; // 4.0.8
const withPreventDoubleClick = (WrappedComponent) => {
class PreventDoubleClick extends React.PureComponent {
debouncedOnPress = () => {
this.props.onPress && this.props.onPress();
}
onPress = debounce(this.debouncedOnPress, 300, { leading: true, trailing: false });
render() {
return <WrappedComponent {...this.props} onPress={this.onPress} />;
}
}
PreventDoubleClick.displayName = `withPreventDoubleClick(${WrappedComponent.displayName ||WrappedComponent.name})`
return PreventDoubleClick;
}
Usage
import { Button } from 'react-native';
import withPreventDoubleClick from './withPreventDoubleClick';
const ButtonEx = withPreventDoubleClick(Button);
<ButtonEx onPress={this.onButtonClick} title="Click here" />
Use property Button.disabled
import React, { Component } from 'react';
import { AppRegistry, StyleSheet, View, Button } from 'react-native';
export default class App extends Component {
state={
disabled:false,
}
pressButton() {
this.setState({
disabled: true,
});
// enable after 5 second
setTimeout(()=>{
this.setState({
disabled: false,
});
}, 5000)
}
render() {
return (
<Button
onPress={() => this.pressButton()}
title="Learn More"
color="#841584"
disabled={this.state.disabled}
accessibilityLabel="Learn more about this purple button"
/>
);
}
}
// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => App);
Here is my simple hook.
import { useRef } from 'react';
const BOUNCE_RATE = 2000;
export const useDebounce = () => {
const busy = useRef(false);
const debounce = async (callback: Function) => {
setTimeout(() => {
busy.current = false;
}, BOUNCE_RATE);
if (!busy.current) {
busy.current = true;
callback();
}
};
return { debounce };
};
This can be used anywhere you like. Even if it's not for buttons.
const { debounce } = useDebounce();
<Button onPress={() => debounce(onPressReload)}>
Tap Me again and adain!
</Button>
Agree with Accepted answer but very simple way , we can use following way
import debounce from 'lodash/debounce';
componentDidMount() {
this.onPressMethod= debounce(this.onPressMethod.bind(this), 500);
}
onPressMethod=()=> {
//what you actually want on button press
}
render() {
return (
<Button
onPress={() => this.onPressMethod()}
title="Your Button Name"
/>
);
}
I use it by refer the answer above. 'disabled' doesn't have to be a state.
import React, { Component } from 'react';
import { TouchableHighlight } from 'react-native';
class PreventDoubleTap extends Component {
disabled = false;
onPress = (...args) => {
if(this.disabled) return;
this.disabled = true;
setTimeout(()=>{
this.disabled = false;
}, 500);
this.props.onPress && this.props.onPress(...args);
}
}
export class ButtonHighLight extends PreventDoubleTap {
render() {
return (
<TouchableHighlight
{...this.props}
onPress={this.onPress}
underlayColor="#f7f7f7"
/>
);
}
}
It can be other touchable component like TouchableOpacity.
If you are using react navigation then use this format to navigate to another page.
this.props.navigation.navigate({key:"any",routeName:"YourRoute",params:{param1:value,param2:value}})
The StackNavigator would prevent routes having same keys to be pushed in the stack again.
You could write anything unique as the key and the params prop is optional if you want to pass parameters to another screen.
The accepted solution works great, but it makes it mandatory to wrap your whole component and to import lodash to achieve the desired behavior.
I wrote a custom React hook that makes it possible to only wrap your callback:
useTimeBlockedCallback.js
import { useRef } from 'react'
export default (callback, timeBlocked = 1000) => {
const isBlockedRef = useRef(false)
const unblockTimeout = useRef(false)
return (...callbackArgs) => {
if (!isBlockedRef.current) {
callback(...callbackArgs)
}
clearTimeout(unblockTimeout.current)
unblockTimeout.current = setTimeout(() => isBlockedRef.current = false, timeBlocked)
isBlockedRef.current = true
}
}
Usage:
yourComponent.js
import React from 'react'
import { View, Text } from 'react-native'
import useTimeBlockedCallback from '../hooks/useTimeBlockedCallback'
export default () => {
const callbackWithNoArgs = useTimeBlockedCallback(() => {
console.log('Do stuff here, like opening a new scene for instance.')
})
const callbackWithArgs = useTimeBlockedCallback((text) => {
console.log(text + ' will be logged once every 1000ms tops')
})
return (
<View>
<Text onPress={callbackWithNoArgs}>Touch me without double tap</Text>
<Text onPress={() => callbackWithArgs('Hello world')}>Log hello world</Text>
</View>
)
}
The callback is blocked for 1000ms after being called by default, but you can change that with the hook's second parameter.
I have a very simple solution using runAfterInteractions:
_GoCategoria(_categoria,_tipo){
if (loading === false){
loading = true;
this.props.navigation.navigate("Categoria", {categoria: _categoria, tipo: _tipo});
}
InteractionManager.runAfterInteractions(() => {
loading = false;
});
};
Did not use disable feature, setTimeout, or installed extra stuff.
This way code is executed without delays. I did not avoid double taps but I assured code to run just once.
I used the returned object from TouchableOpacity described in the docs https://reactnative.dev/docs/pressevent and a state variable to manage timestamps. lastTime is a state variable initialized at 0.
const [lastTime, setLastTime] = useState(0);
...
<TouchableOpacity onPress={async (obj) =>{
try{
console.log('Last time: ', obj.nativeEvent.timestamp);
if ((obj.nativeEvent.timestamp-lastTime)>1500){
console.log('First time: ',obj.nativeEvent.timestamp);
setLastTime(obj.nativeEvent.timestamp);
//your code
SplashScreen.show();
await dispatch(getDetails(item.device));
await dispatch(getTravels(item.device));
navigation.navigate("Tab");
//end of code
}
else{
return;
}
}catch(e){
console.log(e);
}
}}>
I am using an async function to handle dispatches that are actually fetching data, in the end I'm basically navigating to other screen.
Im printing out first and last time between touches. I choose there to exist at least 1500 ms of difference between them, and avoid any parasite double tap.
You can also show a loading gif whilst you await some async operation. Just make sure to tag your onPress with async () => {} so it can be await'd.
import React from 'react';
import {View, Button, ActivityIndicator} from 'react-native';
class Btn extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
}
}
async setIsLoading(isLoading) {
const p = new Promise((resolve) => {
this.setState({isLoading}, resolve);
});
return p;
}
render() {
const {onPress, ...p} = this.props;
if (this.state.isLoading) {
return <View style={{marginTop: 2, marginBottom: 2}}>
<ActivityIndicator
size="large"
/>
</View>;
}
return <Button
{...p}
onPress={async () => {
await this.setIsLoading(true);
await onPress();
await this.setIsLoading(false);
}}
/>
}
}
export default Btn;
My implementation of wrapper component.
import React, { useState, useEffect } from 'react';
import { TouchableHighlight } from 'react-native';
export default ButtonOneTap = ({ onPress, disabled, children, ...props }) => {
const [isDisabled, toggleDisable] = useState(disabled);
const [timerId, setTimerId] = useState(null);
useEffect(() => {
toggleDisable(disabled);
},[disabled]);
useEffect(() => {
return () => {
toggleDisable(disabled);
clearTimeout(timerId);
}
})
const handleOnPress = () => {
toggleDisable(true);
onPress();
setTimerId(setTimeout(() => {
toggleDisable(false)
}, 1000))
}
return (
<TouchableHighlight onPress={handleOnPress} {...props} disabled={isDisabled} >
{children}
</TouchableHighlight>
)
}

Categories

Resources