Make React Native Elements ButtonGroup swipeable - javascript

I want some guidance how to make it possible to swipe between react native elements "Buttongroups".
I found a "swiper" library but cant make it work with the buttongroup.
https://github.com/leecade/react-native-swiper
Have anyone any idé how to implement this ?
I want to be able to both swipe and push one of the buttons.
My Component including ButtonGroup :
class Users extends Component {
constructor () {
super()
this.state = {
selectedIndex: 0,
loading: false
}
this.updateIndex = this.updateIndex.bind(this)
}
// Call fetchList with 0 to get access to all users
componentWillMount () {
let i = 0
this.props.fetchList(i)
this.props.fetchProfileData()
}
// updates the selectedIndex and calls the methods with the selectedindex
updateIndex (selectedIndex) {
this.setState({selectedIndex})
this.fetchAllUsers(selectedIndex)
this.fetchFemale(selectedIndex)
this.fetchMale(selectedIndex)
}
fetchAllUsers (index) {
if (index === 0) {
this.props.fetchList(index)
}
}
fetchFemale (index) {
if (index === 1) {
this.props.fetchList(index)
}
}
fetchMale (index) {
if (index === 2) {
this.props.fetchList(index)
}
}
renderItem ({ item }) {
return <ListUserItem user={item} />
}
render () {
const buttons = ['All', 'Female', 'Male']
const { selectedIndex } = this.state
return (
<ImageBackground
source={require('../../assets/image.jpg')}
style={styles.container}
>
<HeaderBlack />
<View>
<BlurView
style={styles.absolute}
blurType='dark'
blurAmount={0.001}
height={695} />
<View style={{ justifyContent: 'center', alignItems: 'center' }} >
<ButtonGroup
onPress={this.updateIndex.bind(this)}
selectedIndex={selectedIndex}
selectedButtonStyle={{backgroundColor: 'black'}}
buttons={buttons}
containerStyle={{ backgroundColor: 'transparent', height: 23, width: 200, marginTop: 30, marginBottom: -20, justifyContent: 'center', alignItems: 'baseline' }}
textStyle={{fontFamily: 'GeosansLight', color: 'white'}} />
</View>
<View style={{ maxHeight: 580, marginTop: 50 }} >
<FlatList
data={this.props.users}
renderItem={this.renderItem}
/>
</View>
</View>
</ImageBackground>
)
}
}

I usually use hammer.js for any swipe interactions I need to manage. Essentially this library just has you create custom event listeners for things like swiping, panning, pinching, rotating, etc. and just attach them to whatever element you'd like. As long as you have a reference to the element, it shouldn't matter if it is a react element or not, it should work just the same.

Related

How to render 200+ view without performance issue in react-native

I am trying to make a game in react-native. I want to render 200+ views on the Game screen. Each View has a pressable functionality. Whenever I press the View I need to run a function that will change the View background color and update score on the game context. But Whenever I try to press any View it took some time to change the background and update the context.
Note
I am using the expo as a development environment and I am using a real device too.
My View Component
import { useEffect, useState, memo } from "react";
import { useContext } from "react";
import { gameContext } from "./gameContext";
import { Pressable, View } from "react-native";
function CheckBoxCom() {
const [active, setActive] = useState(false);
const { score, setScore } = useContext(gameContext);
useEffect(() => {
let time = setTimeout(() => {
setActive(false);
}, Math.floor(Math.random() * 35000));
return () => clearTimeout(time);
}, [active]);
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score + 1);
};
return (
<View>
<Pressable onPress={handlePress}>
<View
style={{
width: 20,
height: 20,
borderWidth: 2,
borderColor: active ? "green" : "gray",
margin: 3,
borderRadius: 3,
backgroundColor: active ? "green" : null,
}}
></View>
</Pressable>
</View>
);
}
export default memo(CheckBoxCom);
Game Screen Component
import { useContext, useEffect, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, FlatList } from "react-native";
import CheckBox from "./CheckBox";
import { gameContext } from "./gameContext";
export default function Game({ navigation }) {
const { score, time, setTime, boxList } = useContext(gameContext);
const [intervalId, setIntervalId] = useState("");
useEffect(() => {
const int = setInterval(() => {
setTime((prvTime) => prvTime - 1);
}, 1000);
setIntervalId(int);
return () => clearInterval(int);
}, []);
if (time === 0) {
clearInterval(intervalId);
navigation.navigate("Score", { score });
}
return (
<View style={{ flex: 1 }}>
<StatusBar style="auto" />
<View style={styles.textHeader}>
<Text>Score : {score}</Text>
<Text>Time Left: {time}s</Text>
</View>
<View style={styles.checkBoxContainer}>
<FlatList
style={{ alignSelf: "center" }}
data={boxList}
initialNumToRender={50}
numColumns={12}
renderItem={(i) => <CheckBox />}
keyExtractor={(i) => i.toString()}
scrollEnabled={false}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
textHeader: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
marginTop: 40,
paddingHorizontal: 30,
},
checkBoxContainer: {
margin: 20,
display: "flex",
flexWrap: "wrap",
height: "80%",
overflow: "hidden",
flexDirection: "row",
},
});
How can I run view function immediately whenever I press it?
The reason it is slow is that when you press on a view, all 200+ CheckBoxCom components rerender. If they don't need to, we can improve performance by trying to prevent those unnecessary rerenders.
I believe the major bottleneck here is the gameContext. It groups together a lot of states and if any of these were to change, all components will rerender. It provides score state that you are reading within each CheckBoxCom. Whenever the score changes all CheckBoxCom components will re-render. If you change handlePress() to:
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score => score + 1);
};
Please note the use of callback to update the score in the above handler. In this case, we don't need to read score from context, so we can remove it from the game context provider, only pass setScore. Removing score from the context provider is important because not doing so will rerender all components using the context even if you don't specifically destructure score.
Also, make sure you don't have a lot of state variables within a single context. Split it into multiple contexts if you have different states in there. In this way, you will be able to reduce unnecessary rerenders of the CheckBoxCom components.
Since your CheckBoxCom components have an internal state, using React.memo() will not help to prevent rerenders because it only works for rerenders resulting from changed props.
But if you are able to refactor them to lift the active state up to the parent i.e. something like activeViews or something (which could be a map of indexes which are true i.e. active), then you can pass the active state as a boolean prop to each CheckBoxCom component. And if we also pass setScore via a prop instead of via context, we can benefit from React.memo(). BTW it is not necessary to wrap setState methods with useCallback().
The end result will be: CheckBoxCom components with zero internal states and no reliance on context, in other words, pure components i.e. components which work nicely with React.memo().
Use pagination in flatlist
for ref: Pagination in flatlist
import React, { Component } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
FlatList,
Platform,
ActivityIndicator,
} from 'react-native';
export default class App extends Component {
constructor() {
super();
this.state = {
loading: true,
//Loading state used while loading the data for the first time
serverData: [],
//Data Source for the FlatList
fetching_from_server: false,
//Loading state used while loading more data
};
this.offset = 0;
//Index of the offset to load from web API
}
componentDidMount() {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset : "+this.offset);
console.log(responseJson.slice((this.offset*12),((this.offset+1)*12)-1));
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
// serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
//adding the new data with old one available in Data Source of the List
loading: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
}
loadMoreData = () => {
//On click of Load More button We will call the web API again
this.setState({ fetching_from_server: true }, () => {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset Load : "+this.offset);
console.log(responseJson);
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
//serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
fetching_from_server: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
});
};
renderFooter() {
return (
//Footer View with Load More button
<View style={styles.footer}>
<TouchableOpacity
activeOpacity={0.9}
onPress={this.loadMoreData}
//On Click of button calling loadMoreData function to load more data
style={styles.loadMoreBtn}>
<Text style={styles.btnText}>Loading</Text>
{this.state.fetching_from_server ? (
<ActivityIndicator color="white" style={{ marginLeft: 8 }} />
) : null}
</TouchableOpacity>
</View>
);
}
render() {
return (
<View style={styles.container}>
{this.state.loading ? (
<ActivityIndicator size="large" />
) : (
<FlatList
style={{ width: '100%' }}
keyExtractor={(item, index) => index}
data={this.state.serverData}
renderItem={({ item, index }) => (
<View style={styles.item}>
<Text style={styles.text}>
{item.currency}
{'.'}
{item.code}
</Text>
</View>
)}
onEndReached={this.loadMoreData}
onEndReachedThreshold ={0.1}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListFooterComponent={this.renderFooter.bind(this)}
//Adding Load More button as footer component
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: 30,
},
item: {
padding: 10,height:80
},
separator: {
height: 0.5,
backgroundColor: 'rgba(0,0,0,0.4)',
},
text: {
fontSize: 15,
color: 'black',
},
footer: {
padding: 10,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
loadMoreBtn: {
padding: 10,
backgroundColor: '#800000',
borderRadius: 4,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
btnText: {
color: 'white',
fontSize: 15,
textAlign: 'center',
},
});

React native <View> doesn't work inside if statment

function sumbit_Login(val, val1, nav) {
const we = val.length
const we1 = val1.length
if (we <= 15) {
alert('Invaild Email')
return (
<View style={{ width: 50000, height: 50000, backgroundColor: 'red' }}>
</View>
)
}
if (we1 <= 5) {
alert('Invaild Password')
return 0;
}
else {
nav.navigate('Main')
}
// I use it here out the function (inside another function)
<View style={{ marginBottom: 10, width: "80%", marginLeft: '10%', marginTop: '-8%' }}>
<Button
title="Login"
onPress={() => sumbit_Login(value, value1, navigation)}
/>
</View>
The view doesn't work in if statement anyone have another way or solve the problem i use the function inside a button but the 'View' inside return doesn't work (I don't get any error.)
You're not using the View anywhere in the component tree, try to trace your code, you're calling a function onPress, which is a callback and isn't really inside your render hierarchy, if I understand your use case correctly, you're doing this the wrong way..
The way that should be done is to have a state control the visibility of the view when you want to render it and when you don't, here's an example:
function SomeComponent({ navigation }) {
// Create a state for the visiblity
const [visible, setVisible] = useState(false)
function onSubmit(val, val1) {
const we = val.length
const we1 = val1.length
if (we <= 15) {
alert('Invaild Email')
// Set visiblity to true in order to show the View
setVisible(true)
return
}
if (we1 <= 5) {
alert('Invaild Password')
return;
}
else {
navigation.navigate('Main')
}
}
return (
<View style={{ marginBottom: 10, width: "80%", marginLeft: '10%', marginTop: '-8%' }}>
<Button
title="Login"
onPress={() => sumbit_Login(value, value1, navigation)}
/>
{
// Conditionally render the View
visible && <View style={{ width: 50000, height: 50000, backgroundColor: 'red' }}>
}
</View>
)
}
I suggest you read more docs about react and react native, dos and don'ts, and the best practices for dealing with the lifecycle
Hope you find this helpful

How to create custom dropdown list in react-native?

I almost consume 17 hours, but the answers on what I have searched did not satisfy the exact answer to my question,
how to create own custom DropdownList in react native? I already did, the 1st child node which is DropDown (cyan) is work properly, the 2nd child node which is the the List (green) with absolute position but it was not work properly, unless the root parent (red) will also covered the 2nd child node with absolute position, to make it active and clickable, as like what I mention from the picture, Figure 1.0
I set absolute position of the List (green) to make it float from the next child node and the error was persist, the List(parent) was disabled and it cannot click each child items (yellow), unless I set height of the root parent (red) that covered the entire child node but it leads to the next node to adjust the position and do the unexpected layout, like the sample picture Figure 1.1
I DID IT!, the problem is the root parent (red) must not have customized styled property, and I still have one questions, I dont know if it is bug or what, once I did not put any custom property in the style of root parent it works properly, and here is my source code
App.js
import React, { useState } from 'react';
import Separator from './Separator';
import Spinner from './Spinner';
import {
View,
Text,
TouchableOpacity
} from 'react-native';
export default function () {
const [spBusiness, setSPBusiness] = useState({
showList: false,
selectedItem: ''
})
const onShowList = () => {
if (spBusiness.showList) {
setSPBusiness(prev => ({
...prev,
showList: false
}))
}
else {
setSPBusiness(prev => ({
...prev,
showList: true
}))
}
}
const onSelectItem = (item, index) => {
setSPBusiness(prev => ({
showList: false,
selectedItem: item
}));
}
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff' }}>
<Box text='Rectangle 1' onPress={() => console.log('Rect 1')} />
<Separator vertical={1} />
<Spinner
placeholder='Select Item'
items={['A', 'B', 'C']}
isListShow={spBusiness.showList}
selectedItem={spBusiness.selectedItem}
onShowList={onShowList}
onSelectItem={onSelectItem}
/>
<Separator vertical={1} />
<Box text='Rectangle 2' onPress={() => console.log('Rect 2')} />
<Separator vertical={1} />
</View>
)
}
const Box = props => (
<TouchableOpacity onPress={props.onPress} style={{ zIndex: -1, borderRadius: 7.5, width: 250, height: 45, justifyContent: 'center', alignItems: 'center', backgroundColor: '#242424' }}>
<Text style={{ color: '#fff' }}>{props.text}</Text>
</TouchableOpacity>
)
DropDownList/index.js
import React from 'react';
import styles from './style';
import AIcon from 'react-native-vector-icons/AntDesign';
import {
View,
Text,
Dimensions,
TouchableOpacity,
ScrollView
} from 'react-native';
import Separator from '../Separator';
export default function (props) {
const { items } = props;
const { width, height } = Dimensions.get('window');
const size = size => (size / 100) * width;
const hp = hp => (hp / 100) * height;
const wp = wp => (wp / 100) * width;
return (
<View>
{/* here is the root parent with empty style property*/}
<TouchableOpacity
style={styles.skin}
activeOpacity={0.75}
onPress={props.onShowList}
>
<View style={styles.body}>
<View style={styles.upperBodyPane}>
</View>
<View style={styles.centerBodyPane}>
{props.selectedItem.length === 0 &&
<Text>{props.placeholder}</Text>
}
{props.selectedItem.length !== 0 &&
<Text>{props.selectedItem}</Text>
}
<AIcon name='caretdown' size={size(3.5)} color='#242424' />
</View>
</View>
</TouchableOpacity>
{props.isListShow &&
<ScrollView
style={styles.list}
contentContainerStyle={styles.listContainer}
>
{
items.map((item, index) => (
<View key={index}>
<Item
item={item}
onPress={() => props.onSelectItem(item, index)}
/>
{items.length !== (index + 1) ? <Separator vertical={0.5} /> : null}
</View>
))
}
</ScrollView>
}
</View>
)
}
const Item = props => (
<TouchableOpacity style={styles.item} onPress={props.onPress}>
<Text style={styles.itemText}>{props.item}</Text>
</TouchableOpacity>
)
DropDownList/style.js
import {
Dimensions,
StyleSheet
} from 'react-native';
const { width, height } = Dimensions.get('window');
const size = size => (size / 100) * width;
const hp = hp => (hp / 100) * height;
const wp = wp => (wp / 100) * width;
export default StyleSheet.create({
skin: {
minWidth: wp(70),
height: hp(5.5),
paddingHorizontal: wp(6),
justifyContent: 'center',
backgroundColor: 'cyan',
position: 'relative'
},
body: {
},
centerBodyPane: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
},
item: {
height: hp(5),
paddingHorizontal: wp(5),
justifyContent: 'center',
backgroundColor: 'orange',
},
itemText: {
color: 'white'
},
list: {
top: hp(6.5),
width: wp(70),
maxHeight: hp(15),
backgroundColor: 'green',
position: 'absolute',
},
listContainer: {
padding: size(1.75),
}
})
but the worse case only, I need to set the next child node of DropDownList to zIndex: -1 , but it finally works.

How to deselect an option that comes from Array if another one is selected

So I have a component that will render my cards called CardListing as bellow;
return getWalletPayment.map(payment => (
<CardListing
key={payment._id}
card={payment.card}
cardNo={payment.cardNo}
onChanged={selected => {
this.setState({ selectedCard: selected });
}}
/>
));
For now it will render two cards. If I select one everything is fine but if I select the second one the first one will stay selected until I tap on it again to deselect it.
Here is the implementation code
export default class CardListing extends Component {
constructor(props) {
super(props);
this.state = {
selected: false,
scaleCheckmarkValue: new Animated.Value(0)
};
this.scaleCheckmark = this.scaleCheckmark.bind(this);
this.selectPaymentOption = this.selectPaymentOption.bind(this);
}
scaleCheckmark(value) {
Animated.timing(this.state.scaleCheckmarkValue, {
toValue: value,
duration: 400,
easing: Easing.easeOutBack
}).start();
}
selectPaymentOption() {
const { selected } = this.state;
this.setState({
selected: !this.state.selected
});
this.props.onChanged(selected);
}
render() {
const { selected, scaleCheckmarkValue } = this.state;
const { card, cardNo } = this.props;
const number = cardNo.substring(15);
let logo;
if (card == "visa") {
logo = require("../../../assets/images/visa.png");
}
if (card == "master-card") {
logo = require("../../../assets/images/mastercard.png");
}
if (card == "amex") {
logo = require("../../../assets/images/amex.png");
}
if (card == "jcb") {
logo = require("../../../assets/images/jcb.png");
}
if (card == "discover") {
logo = require("../../../assets/images/discover.png");
}
const iconScale = this.state.scaleCheckmarkValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0.01, 1.6, 1]
});
const scaleValue = selected ? 1 : 0;
this.scaleCheckmark(scaleValue);
return (
<View>
<TouchableOpacity
onPress={() => this.selectPaymentOption(this, cardNo)}
style={styles.paymentOptionItem}
>
<View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between"
}}
>
<View style={{ flexDirection: "row" }}>
<Image
source={logo}
style={{
width: 40,
height: 30,
marginTop: 3
}}
/>
<View
style={{
flexDirection: "column"
}}
>
<Text style={styles.paymentOptionTitle}>
{card.toUpperCase()}
</Text>
<Text style={styles.paymentOptionTitle}>Ending {number}</Text>
</View>
</View>
<Animated.View
style={[
{ transform: [{ scale: iconScale }] },
styles.iconWrapper
]}
>
<Icon name="check" color={colors.black} size={20} />
</Animated.View>
</View>
</View>
</TouchableOpacity>
<View style={styles.divider} />
</View>
);
}
}
Any idea how to solve this?
Consider externalising the 'selected' state from the <CardListing/> component, so that the parent component tracks which credit card is currently selected (rather than each <CardListing/> tracking internal state to determine if it is selected).
First lift the selected outside of <CardListing/>, to the parent component. This will involve changes to your parent components map/render function, as shown:
/*
Add this selectedCardId state to parent wallet component and
pass via selectedId. Also pass cardId prop so CardListing can
determine if it is the selected card
*/
return getWalletPayment.map(payment => (
<CardListing
key={payment._id}
card={payment.card}
cardNo={payment.cardNo}
cardId={payment._id}
selectedId={ this.state.selectedCard }
onChanged={selectedId => {
this.setState({ selectedCard: selectedId});
}}
/>
));
Next, revise the implementation of <CardListing/> so that selected is retrieved from this.props rather than this.state, and so that the cardId of the selected card is passed back via the onChanged() callback like so:
selectPaymentOption() {
/*
Pass the id of the card to be selected to callback
*/
this.props.onChanged( this.props.cardId);
}
render() {
/*
Determine if this card should be rendered a the selected
card
*/
const selected = this.selectedId === this.cardId;
/*
Remaining render method remains unchanged
*/
}
Hope this helps!

React Native FlatList with clickable items

I'm building a custom line style for a react-native FlatList.
I want the user to navigate to item details when clicking on the line text, or navigate to another page (drill down next level) when clicked on the right caret, something like:
Here is my current list components code:
class MyList extends Component {
handleShowDetails = index => {
...
};
handleDrillDown = index => {
...
}
render = () => {
let data = // Whatever data here
return (
<View style={styles.container}>
<FlatList
data={data}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<MyListItem
onTextPress={this.handleShowDetails}
onCaretPress={this.handleDrillDown}
item={item}
/>
)}
/>
</View>
);
};
}
export default MyList;
const styles = StyleSheet.create({
container: {
flex: 1
},
item: {
padding: 10,
fontSize: 18,
height: 44,
backgroundColor: colors.white,
borderStyle: "solid",
marginBottom: 1
}
});
And my list item component:
class MyListItem extends Component {
handleTextPress = () => {
if (this.props.onTextPress) this.props.onTextPress(this.props.item.id);
};
handleIconPress =() => {
if (this.props.onCaretPress) this.props.onCaretPress(this.props.item.id)
}
render = () => {
return (
<View style={styles.container}>
<View style={styles.textContainer}>
<Text onPress={this.handleTextPress}>{this.props.item.title}</Text>
</View>
<View style={styles.iconContainer}>
<Button onPress={this.handleIconPress}>
<Icon name="ios-arrow-forward"/>
</Button>
</View>
</View>
);
};
}
export default MyListItem;
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
backgroundColor: colors.white,
marginBottom: 1,
height: 30
},
textContainer: {
backgroundColor: colors.light,
paddingLeft: 5,
fontSize: 26
},
iconContainer: {
alignSelf: "flex-end",
backgroundColor: colors.primary,
paddingRight: 5,
fontSize: 26
}
});
Problems I'm facing:
a. Clicking on text is not working properly. Sometimes it navigates but most of time I cannot navigate, specially if I click on the empty space between the text and the caret.
b. I simply cannot style the fontSize of the text.
c. I'm not being able to space then accordingly.
d. I need to vertical center both itens on row.
Here is what I'm getting for now:
For the clicking issue, you could set an TouchableHighlight or a TouchableOpacity for the View.
For spacing and alignment issues, maybe you could set the text to flex: 9 and the icon to flex: 1 with FlexBox. Here are the docs for that https://facebook.github.io/react-native/docs/flexbox.html.
a. Try to make the text full the cell. I can see your text just in 50% of the cell. style <Text> with height: 44
b. fontsize has to be placed in the <Text> component. You are placing it in <View> component
c. "I'm not being able to space then accordingly". I can't get your point clearly.
d. Try justifyContent: 'center' in iconContainer
For part a I would suggest an improvement over Anthu's answer:
I you want the entire textContainer to be clickable I suggest using TouchableWithoutFeedback (or another Touchable which suits your needs) and wrap this around the textContainer View

Categories