Animated state changes without cycling values using interpolation - react-native

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)

Related

How to update the style after useState update - 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 !

withTiming callback called on render, not when animation finished

I've started to play around with react-native-reanimated v2.
const fontSize = useSharedValue(25);
const config = {
duration: 500,
easing: Easing.bezier(0.5, 0.01, 0, 1),
};
const fontStyle = useAnimatedStyle(() => {
return {
fontSize: withTiming(fontSize.value, config, (isFinished) => {
if (isFinished) {
console.log('isFinished');
}
}),
};
});
return (
<Button
title="toggle"
onPress={() => {
fontSize.value = 1;
}}
/>
)
The above code block initially sets the font size to 25, and on press starts an animation where it gradually reduces it to 1.
When the animation is finished, I want to change the text.
However, the callback with isFinished is called instantly, before I press the button. The moment the app renders.
The code block is pretty much from their docs, so I'm unsure what I'm doing wrong.
Is it possible to make sure the callback is only called after the animation is finished? Not on render?
UPDATE
A hack I've put together is setting a state const [buttonPressed, setButtonPressed] = useState(false); and checking for this along with isFinished, and setting this to true when the button has been pressed.
But this is a horrible workaround.

Need assistance fixing maximum update depth exceeded issue

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])
...
}

How can I pass the current theme of the app into StyleSheet.create()?

Due to various limitations, I cannot store the current theme in props. I have to use a custom props that I cannot modify at the moment. I have been able to get around this by using React Context to store the current theme.
I have a class called pageStyle. In it, I have a pageContainer that I'd like to be able to set backgroundColor either to dark or light values.
I am able to get the current theme in a separate class R by using this.context.theme. R is also where I am rendering the FlatList object and passing style={pageStyle.pageContainer}.
I could create two separate pageContainers, called pageContainerDark and pageContainerLight, and have the correct respective backgroundColors for both. However, this feels messy to me. This would also require having a method for each style page to determine which pageContainer to use and is not scalable whatsoever. I have also tried using a global variable but was having some issues with this.
Is there a cleaner way to set pageContainer's backgroundColor based on the current theme?
I have attached the pageStyle.ts file below.
//pageStyle.ts
import { StyleSheet} from 'react-native';
const pageStyles = StyleSheet.create({
pageContainer: {
flex: 1,
backgroundColor: "#232323"
},
// pageContainerDark: {
// flex: 1,
// backgroundColor: "#232323"
// },
// pageContainerLight: {
// flex: 1,
// backgroundColor: "#FFFFFF"
// },
pageContentContainer: {
paddingBottom: 0
}
});
// export function getPageContainerFromPageStyles(theme: string) {
// if (theme == 'light') {
// return pageStyle.pageContainerForLightTheme
// } else {
// return pageStyle.pageContainerForDarkTheme
// }
// }
export { pageStyle };

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