I'm trying to write a hacky fix to ScrollableTabView since it isn't playing nice with the function that triggers when there's a tab switch. When I replace the setState with console.log I see that it only triggers once with every tab switch so it's not looping infinitely like the error is complaining.
Parent container
state = {
headerName: 'Loading',
}
setHeader = (header) => {
this.setState({'headerName': header})
}
render () {
return (
<ScrollableTabView
renderTabBar={() => <BottomTabBar setHeader={this.setHeader} headerNames={['A','B','C']} />}
>
)
}
BottomTabBar
render() {
this.props.setHeader(this.props.headerNames[this.props.activeTab])
...
}
Related
Im looking for insight on how best to implement a loadMore function in the onEndReached callback provided by flatlist while using apollo hooks! I've got it sort of working except every time i load more results the list jumps to the top since the data field of flatlist relies on incoming data from useQuery that changes every time it asks for more...
I dont know if i should be implementing offset and limit based pagination, cursor based, or some other strategy.
If anyone has tips that would be huge! thanks!
I am using Shopify storefront graphql queries to get product list, and here is how I have implemented pagination using cursor-based pagination method on FlatList. Hope you find something usable.
First, declare two variables which will be used later to check whether the Flatlist scrolled and reached on the end.
// declare these two variables
let isFlatlistScrolled = false;
let onEndReachedCalledDuringMomentum = false;
Now, create a method called handleFlatlistScroll which will be used to changed the values of the variable isFlatlistScrolled when the flatlist is scrolled.
const handleFlatlistScroll = () => {
isFlatlistScrolled = true;
};
Also declare a method to change the value of onEndReachedCalledDuringMomentum.
const onMomentumScrollBegin = () => {
onEndReachedCalledDuringMomentum = false;
}
Now, create your flatlist like this :
return (
<Layout style={{ flex: 1 }}>
<Query
query={GET_PRODUCT_LIST_BY_COLLECTION_HANDLE}
variables={{
handle: props.route.params.handle,
cursor: null,
}}>
{({
loading,
error,
data,
fetchMore,
networkStatus,
refetch,
stopPolling,
}) => {
if (loading) {
return <ProductListPlaceholder />;
}
if (data && data.collectionByHandle?.products?.edges?.length > 0) {
stopPolling();
return (
<FlatList
data={data.collectionByHandle.products.edges}
keyExtractor={(item, index) => item.node.id}
renderItem={renderProductsItem}
initialNumToRender={20}
onScroll={handleFlatlistScroll}
onEndReached={() => {
if (
!onEndReachedCalledDuringMomentum &&
isFlatlistScrolled &&
!isLoadingMoreProducts &&
!loading &&
data.collectionByHandle?.products?.pageInfo?.hasNextPage
) {
onEndReachedCalledDuringMomentum = true;
setLoadingMoreProductsStatus(true);
// your loadmore function to fetch more products
}
}}
onEndReachedThreshold={Platform.OS === 'ios' ? 0 : 0.1}
onMomentumScrollBegin={onMomentumScrollBegin}
// ... your other flatlist props
/>
);
}
return <EmptyProductList />;
}}
</Query>
</Layout>
)
As you can see in above code, load more function only called when flatlist is properly scrolled at the end.
I need to perform different action on single and double tap on a view. On double tap I need to like the image just like Instagram double tap experience. On single tap I need to open a modal.
For double tap I have used TapGestureHandler which works perfect
<TapGestureHandler
ref={this.doubleTapRef}
maxDelayMs={200}
onHandlerStateChange={this.onProductImageDoubleTap}
numberOfTaps={2}
>
<SomeChildComponent ...
But when I add any Touchable to detect single tap in the
<TouchableWithoutFeedback onPress={this.imageTapped}>
on double tapping the this.imageTapped function is called twice along with this.onProductImageDoubleTap. Is there any way to cancel tap on touchable when two taps are done is quick succession
The best solution is not using state, since setting state is asynchronous.Works like a charm for me on android !
let lastPress = 0;
const functionalComp = () => {
const onDoublePress = () => {
const time = new Date().getTime();
const delta = time - lastPress;
const DOUBLE_PRESS_DELAY = 400;
if (delta < DOUBLE_PRESS_DELAY) {
// Success double press
console.log('double press');
}
lastPress = time;
};
return <View
onStartShouldSetResponder =
{(evt) => onDoublePress()}>
</View>
}
2022 update
This is a performant native solution without any JS thread blocking calculation!
Many more tips here
const tap = Gesture.Tap()
.numberOfTaps(2)
.onStart(() => {
console.log('Yay, double tap!');
});
return (
<GestureDetector gesture={tap}>
{children}
</GestureDetector>
);
The best solution is use react-native-gesture-handler
https://github.com/software-mansion/react-native-gesture-handler
Here is my solution -
import {State, TapGestureHandler} from 'react-native-gesture-handler';
export const DoubleTap = ({children}: any) => {
const doubleTapRef = useRef(null);
const onSingleTapEvent = (event: any) => {
if (event.nativeEvent.state === State.ACTIVE) {
console.log("single tap 1");
}
};
const onDoubleTapEvent = (event: any) => {
if (event.nativeEvent.state === State.ACTIVE) {
console.log("double tap 1");
}
};
return (
<TapGestureHandler
onHandlerStateChange={onSingleTapEvent}
waitFor={doubleTapRef}>
<TapGestureHandler
ref={doubleTapRef}
onHandlerStateChange={onDoubleTapEvent}
numberOfTaps={2}>
{children}
</TapGestureHandler>
</TapGestureHandler>
);
};
Now we will wrap the component where we need to detect the double and single tap : -
<DoubleTap>
<View>
...some view and text
</View>
</DoubleTap>
The package react-native-double-tap seems to be what you are looking for.
since you are asking on handling one tap and double tap, here's a simple code i think should covered your issue
Untested
first defined clickCount:0 in state:
state={clickCount:0, //another state}
then create a function with setTimeout to handling if user tapping once or two times:
handlingTap(){
this.state.clickCount==1 ?
//user tap twice so run this.onProductImageDoubleTap()
this.onProductImageDoubleTap :
//user tap once so run this.imageTapped with setTimeout and setState
//i set 1 sec for double tap, so if user tap twice more than 1 sec, it's count as one tap
this.setState({clickCount:1}, ()=>{
setTimeout(()=>{
this.setState({clickCount:0})
this.imageTapped()
}, 1000)
})
}
<TouchableWithoutFeedback onPress={this.handlingTap()}/>
just used TouchableWithoutFeedback instead of TapGestureHandler
With hooks:
const [lastPressed, setLastPressed] = useState(0);
const handlePress = useCallback(() => {
const time = new Date().getTime();
const delta = time - lastPressed;
setLastPressed(time);
if (lastPressed) {
if (delta < DOUBLE_PRESS_DELAY) {
console.log('double press');
} else {
console.log('single press');
}
}
}, [lastPressed]);
I have modified flix's answer into this. By this way, you can catch one and double click separately. I also changed debounce into 300ms which is fairly well for one and double click.
state={clickCount:0, //another state}
Binding context into the handlingTap method
constructor(props){
super(props)
this.handlingTap = this.handlingTap.bind(this)
}
With this function you can catch them separately
handlingTap() {
this.state.clickCount === 1
? this.doubleClick() // This catches double click
: this.setState(state => ({ clickCount: state.clickCount + 1 }), () => {
setTimeout(() => {
if (this.state.clickCount !== 2) {
this.oneClick() // this catches one click
}
this.setState({ clickCount: 0 })
}, 300)
})
}
In the button you can use this way
<TouchableWithoutFeedback onPress={this.handlingTap}></TouchableWithoutFeedback>
export default class App extends React.Component {
constructor() {
super();
this.itemRef = firebaseApp.database().ref();
this.state = {
myArray: [],
};
}
componentDidMount() {
//get database from firebase
//................
this.setState({
// load database into myArray
// Example:
//myArray: myDatabaseFirebase
})
}
render() {
return (
<ViewPager>
{this.state.myArray.map(item => {
return (
<Text>Something from database </Text>
)
})}
</ViewPager>
);
}
}
when I run app it show blank screen, I think myArray did not load database so it null and the app show blank screen. How do I solve this problem ?
On the first render, UI gets loaded and due to the async nature of your firebase data it takes time to get load hence you need to check whether the data is available or not in myArray. Since you have defined myArray as empty initially So we can directly check the length. just add condition
return (
<ViewPager>
{this.state.myArray.length > 0 && this.state.myArray.map(item => {
return (
<Text>Something from database </Text>
)
})}
</ViewPager>
);
You need to learn JS and understand Sync/Async.
You are updating your array outside of your listener callback. You should setState inside of callback.
In the render method check if my array is not null and not empty
return (
<ViewPager>
{(this.state.myArray&&this.state.myArray.length>0)?
this.state.myArray.map(item => {
return (
<Text>Something from database </Text>
)
})
:<Text>No data present</Text>
}
</ViewPager>
);
I am using ReactContext and Hooks to show and hide a Modal on click of a button.
Following is my Context code
const setPrivacyPolicyModalVisibility = dispatch => {
return ({visible}) => {
visible
? dispatch({type: 'enablePrivacyPolicyModalVisibility'})
: dispatch({type: 'disablePrivacyPolicyModalVisibility'});
};
};
And the reducer code for the same is something as follows
case 'enablePrivacyPolicyModalVisibility':
return {...state, ...{enablePrivacyPolicy: true}};
case 'disablePrivacyPolicyModalVisibility':
return {...state, ...{enablePrivacyPolicy: false}};
Some setup code in my class
const {state, setPrivacyPolicyModalVisibility} = useContext(Context);
const [privacyVisibility, setPrivacyVisibility] = useState(false);
on click of button I am calling the following code
<TouchableOpacity
onPress={() => {
setPrivacyVisibility(true);
console.log(`${privacyVisibility}`);
setPrivacyPolicyModalVisibility({privacyVisibility});
}}.....
As you can see I am console logging the privacyVisibility value but it is always false which I fail to understand
Following is my code in the component to hide or show the Modal
{state.enablePrivacyPolicy ? (
<SettingsPrivacyModal visible={true} />
) : (
<SettingsPrivacyModal visible={false} />
)}
The Modal code is proper as I have tried setting default value to true just to check if modal is visible then it works, but on click of button press the state value does not change and I am not able to see the modal as the value is always false
The issue seems to be in the onPress callback:
onPress={() => {
const privacyVisibility_new = !privacyVisibility;
console.log( privacyVisibility_new );
setPrivacyVisibility( privacyVisibility_new );
setPrivacyPolicyModalVisibility( privacyVisibility:privacyVisibility_new );
}}
When the cycle reaches the callback privacyVisibility has the default which is false. I think you are assuming that once setPrivacyVisibility is called, the privacyVisibility variable will have the new value in that same cycle; but it won't have the updated value until the component renders again.
setPrivacyPolicyModalVisibility doesn't seem to be correct. I am not sure where is dispatch exactly, but assuming it is at the same level as the function you can simply use it inside.
const setPrivacyPolicyModalVisibility = visible => {
if ( visible ) {
dispatch({ type: "enablePrivacyPolicyModalVisibility" });
} else {
dispatch({ type: "disablePrivacyPolicyModalVisibility" });
}
};
You might want to simplify your reducer and send directly the visible value:
const setPrivacyPolicyModalVisibility = visible =>
dispatch({ type: "setPrivacyPolicyModalVisibility", payload: visible });
.
case 'setPrivacyPolicyModalVisibility':
return { ...state, is_privacyPolicy_visible: action.payload };
Actually the error was simple. I am using the visible parameter as props in setPrivacyPolicyModalVisibility but while setting I am passing prop of different name
Thanks to #Alvaro for pointing me in the right direction
I don't know how to pass a reference to the TripList instance below to the AddTrip component. I need to do something like that to signal to TripList to refresh the data after adding a new trip.
In my render() method, inside <Navigator> I have:
if (route.index === 1) {
return <TripList
title={route.title}
onForward={ () => {
navigator.push({
title: 'Add New Trip',
index: 2,
});
}}
onBack={() => {
if (route.index > 0) {
navigator.pop();
}
}}
/>
} else {
return <AddTrip
styles={tripStyles}
title={route.title}
onBack={() => { navigator.pop(); }}
/>
}
However, when I call onBack() in AddTrip, after adding a trip, I want to call refresh() on TripList so the new trip is displayed. How best can I structure things to do that? I'm guessing I need to pass TripList somehow to AddTrip and then I can call refresh() there easily right before calling onBack().
This is not how React works. You don't pass instances of a component around, rather you pass the data to your component via props. And your component AddTrip should receive another props which is a function to call when adding a trip.
Let me illustrate this with a code example, this is not how your code should be in the end, but it'll illustrate how to contain the data outside of your components.
// Placed at the top of the file, not in a class or function.
let allTrips = [];
// Your navigator code.
if (route.index === 1) {
return <TripList
trips={allTrips}
title={route.title}
onForward={ () => {
navigator.push({
title: 'Add New Trip',
index: 2,
});
}}
onBack={() => {
if (route.index > 0) {
navigator.pop();
}
}} />
} else {
return <AddTrip
styles={tripStyles}
title={route.title}
onAdd={(tripData) => {
allTrips = [...allTrips, tripData];
}}
onBack={() => { navigator.pop(); }} />
}
As you can see, the logic about adding and finding the trips comes from the parent component, which is the navigator in this case. You will also note that we are reconstructing the content of allTrips, this is important as React is based on the concept of immutability.
You must have heard of Redux which is a system allowing all your components to discuss with a global store from which you fetch and save all your application state. It's a bit more complex that's why I did not use it as an example it.
I'll almost forget the most important! You will not need to signal to to your component that it needs refreshing, the magic of React should take care of it by itself!