How to update the style after useState update - React Native - react-native

I am currently facing a problem that does bother me a lot.
I am using the react-native-calendar-strip library. I would like to have the ability to deselect a date (update the day container from an orange container to a fully transparent one) which is not natively supported.
So I implemented the code bellow which does not work :
import CalendarStrip from "react-native-calendar-strip";
const EventCalendar = (props) => {
const [selectedDate, setSelectedDate] = useState("");
const [deselectDate, setDeselectDate] = useState(false);
const handleDaySelection = (date) => {
date = date.toLocaleString("fr-FR", option);
if (date == selectedDate) {
setSelectedDate("");
setDeselectDate(true);
} else {
setSelectedDate(date);
setDeselectDate(false);
}
}
return (
<View>
<CalendarStrip
onDateSelected={handleDaySelection}
daySelectionAnimation={{
type: "background",
duration: 200,
highlightColor: deselectDate ?
"rgba(0, 0, 0, 0)" : "#fca051",
}}
/>
In fact, the useState does not have the time to update before the daySelectionAnimation prop is called.
After having tried different things, I actually do not have anymore any ideas that could handle this problem.
Do you have any suggestions to help me handle this problem ?
Thanks guys !

Related

if-else statement inside jsx not working as expected

I am trying to animate an image in and out when clicked using React Native reanimated, but the JSX if else condition is not working quite right. Below is the code that works, but only works when clicked for the first time. The state toggled is set to true, so when clicked again it should set the size back to the original and the image should animate back to it and vice versa.
The setup is like so:
export default () => {
const newNumber = useSharedValue(100);
const [Toggled, setToggled] = useState(false);
const style = useAnimatedStyle(() => {
return {
width: withSpring(newNumber.value),
height: withSpring(newNumber.value, { damping: 10,
stiffness: 90, }),
};
});
onPress={() => {
{Toggled ? newNumber.value = 100 : newNumber.value = 350; setToggled(true)}
}}
The problem is when I try to add the newNumber.Value = 100 when setting Toggled to false, it gives me an error. I try to add it like this :
{Toggled ? newNumber.value = 100; setToggled(false) : newNumber.value = 350; setToggled(true)}
Why does it accept the first one but not the second?
If I use this, it works, but can it be done the other way?
const isToggled = Toggled;
if (isToggled) {
// alert('Is NOT Toggled');
newNumber.value = 100;
setToggled(false)
} else {
// alert('Is Toggled');
newNumber.value = 350;
setToggled(true)
}
Thanks
It looks you are just setting a value to newNumber base on Toggled, and then setting the Toggled to its opposite value. So why not do
onPress={() => {
newNumber.value = Toggled ? 100 : 350
setToggled(prev=>!prev)
}}

Reset the initial state value in Recat Native

Requirement: I have to blink a View for 2 sec with 2 different color (for ex red and white).
I can do this by using this code -
const [state, setState] = React.useState(false)
const [initialState, setInitialState] = React.useState(0)
React.useEffect(() => {
if (initialState < 2){
let interval = setInterval(() => {
setState(true)
setInitialState(initialState + 1)
setTimeout(() => {
setState(false)
}, 80);
}, 300);
setTimeout(() => {
clearInterval(interval)
}, 600);
}
}, [initialState])
and called it like -
<View style={{...styles.mainContainer, backgroundColor: state ? Colors.GRO7 : Colors.GRC9}}>
Another Requirement: I have another screen from it i an change the address, on successful address change i have to blink this view again for 2 sec. I'm not sure where i can reset the initial value to 0 again.
I am new In react native, could some one guide me how to achieve this functionality
Can‘t completely understand what‘s your target.
Here is a blinking text sample
you can pass in the initial value to this component instead of defining 0 in this line:
const [initialState, setInitialState] = React.useState(0)
you could have a param to pass in and put it instead of 0 so that every time that param changes this component will re render. and so you get a new initial state.
for example:
const [initialState, setInitialState] = React.useState(initialValue)

Animated state changes without cycling values using interpolation

I'm trying to implement a simple color transition in react native. The current use case is for an Animated.TextInput, I want the backgroundColor to transition depending on the state. Let's say the component can have the following visual states:
state
description
normal
unfocussed input field
error
unfocussed input field with an error
warning
unfocussed input field with a warning
focus
focussed input field disregarding the error/warning flags
A simplified version of such component:
import React, { useState, useEffect } from 'react'
import { Animated, TextInput as RNTextInput } from 'react-native'
const AnimatedTextInput = Animated.createAnimatedComponent(RNTextInput)
interface Props {
errorMessage?: string
warningMessage?: string
}
enum IndicatorState {
Normal,
Error,
Warning,
Focus
}
export const TextInput: React.FunctionComponent<Props> = (props) => {
const [isInFocus, setIsInFocus] = useState(false)
useEffect(() => {
Animated.timing(indicatorStateAnim, {
toValue: getIndicatorState(),
duration: 100,
useNativeDriver: false
})
}, [props.errorMessage, props.warningMessage, isInFocus])
const getIndicatorState = (): IndicatorState => {
if (isInFocus) return IndicatorState.Focus
if (props.errorMessage) return IndicatorState.Error
if (props.warningMessage) return IndicatorState.Error
return IndicatorState.Normal
}
const indicatorStateAnim = new Animated.Value(getIndicatorState())
return (
<AnimatedTextInput
onFocus={() => setIsInFocus(true)}
onBlur={() => setIsInFocus(false)}
style={{
backgroundColor: indicatorStateAnim.interpolate({
inputRange: [
IndicatorState.Normal,
IndicatorState.Error,
IndicatorState.Warning,
IndicatorState.Focus
],
outputRange: [
'#e3e3e3',
'#ff0000',
'#ffff00',
'#0000ff'
]
})
}}
/>
)
}
Using this method switching between two states does transition but it does so by cycling through the interpolated values (as expected of course). A little diagram showing the logic and difference between the current – expected – situation and the desired outcome of transitions.
Possible transition graphed in current and desired situation:
Example of transition between states in current and desired situation:
In short; I'm looking for a solution for transitioning between multiple visual states using an animation without cycling through interpolated values (a.k.a. disco input fields)

How to differentiate between double tap and single tap on react native?

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>

move the view up when keyboard as shown in react-native

hai i am trying to move the view up when keyboard as shown using react-native,I followed the #sherlock's comment in (How to auto-slide the window out from behind keyboard when TextInput has focus? i got an error like this
I don't know how to resolve this error, can any one help me how to resolve this, any help much appreciated.
There's a great discussion about this in the react-native github issues
https://github.com/facebook/react-native/issues/3195#issuecomment-147427391
I'd start there, but here are a couple more links you may find useful, one of which is mentioned already in the article you referenced...
[React Tips] Responding to the keyboard with React Native
Andr3wHur5t/react-native-keyboard-spacer
In my library "react-native-form-generator" (https://github.com/MichaelCereda/react-native-form-generator) i did the following.
I created a Keyboard Aware scroll view (partially modified from https://github.com/facebook/react-native/issues/3195#issuecomment-146518331)
the following it's just an excerpt
export class KeyboardAwareScrollView extends React.Component {
constructor (props) {
super(props)
this.state = {
keyboardSpace: 0,
}
this.updateKeyboardSpace = this.updateKeyboardSpace.bind(this)
this.resetKeyboardSpace = this.resetKeyboardSpace.bind(this)
}
updateKeyboardSpace (frames) {
let coordinatesHeight = frames.endCoordinates.height;
const keyboardSpace = (this.props.viewIsInsideTabBar) ? coordinatesHeight - 49 : coordinatesHeight
this.setState({
keyboardSpace: keyboardSpace,
})
}
resetKeyboardSpace () {
this.setState({
keyboardSpace: 0,
})
}
componentDidMount () {
// Keyboard events
DeviceEventEmitter.addListener('keyboardWillShow', this.updateKeyboardSpace)
DeviceEventEmitter.addListener('keyboardWillHide', this.resetKeyboardSpace)
}
componentWillUnmount () {
DeviceEventEmitter.removeAllListeners('keyboardWillShow')
DeviceEventEmitter.removeAllListeners('keyboardWillHide')
}
scrollToFocusedInput (event, reactNode, extraHeight = 69) {
const scrollView = this.refs.keyboardScrollView.getScrollResponder();
setTimeout(() => {
scrollView.scrollResponderScrollNativeHandleToKeyboard(
reactNode, extraHeight, true
)
}, 220)
}
render () {
return (
<ScrollView
ref='keyboardScrollView'
keyboardDismissMode='interactive'
contentInset={{bottom: this.state.keyboardSpace}}
showsVerticalScrollIndicator={true}
style={this.props.style}>
{this.props.children}
</ScrollView>
)
}
Then i use it like any other scrollview
import { KeyboardAwareScrollView } from 'react-native-form-generator'
...
handleFormFocus(event, reactNode){
this.refs.scroll.scrollToFocusedInput(event, reactNode)
}
...
<KeyboardAwareScrollView ref='scroll'>
<Form ref='registrationForm'
onFocus={this.handleFormFocus.bind(this)}
onChange={this.handleFormChange.bind(this)}
label="Personal Information">
........
</Form>
</KeyboardAwareScrollView>
on change my component (Form) will call scrollToFocusedInput in KeyboardAwareScrollView (using the ref).
i suggest to check the code of my library (see the link on top), or simply use it (everything it's already tested and working).
If you have further questions just comment