React native how to Animate Text and Image - react-native

Hi everyone I just want to animate text and image. This code I wrote is animating at the beginnig.When I update the state, animation is gone just changing image and text. How can I fix it.
//the package I installed from npm
import * as Animatable from 'react-native-animatable';
//the images array changes by state
var arr=[{image:require('./assets/img1.png')},{image:require('./assets/img2.png')},{image:require('./assets/img3.png')}];
//The initial state
state = {
uri: require('./assets/img1.png'),
message:"-",
img_rank:0
};
//In onpress method of TocuhableOpacity
<TouchableOpacity
onPress={() => {
const randomIndex = Math.floor(Math.random() * 3);
//state of message
this.setState({ message: "Act1" });
//state of image rank from the array
this.setState({ img_rank: randomIndex })
...
Showing the text and image
//it's showing the image with animation at the beginning
<Animatable.Image source={arr[this.state.img_rank].image} animation="zoomInUp">
</Animatable.Image>
//the text but it's not animating
<Animatable.Text animation="zoomInUp">
<Text>{this.state.message} </Text>
</Animatable.Text>
Thank you.

Firstly, get your animated component's ref into a variable in your class like animatedImageRef.
<Animatable.Image
ref={r => this.animatedImageRef = r}
source={arr[this.state.img_rank].image}
animation={this.state.img_rank ? 'zoomInUp' : undefined}>
</Animatable.Image>
Then, in your onPress function, use this ref to animate your component again.
this.animatedImageRef.startAnimation(500, 0, () => { })

Related

How to make a React Native TextInput change Opacity just like TouchableOpacity?

My code looks something like this currently:
<View>
<TextInput placeholder='PlaceholderText'>
</TextInput>
</View>
I want to make a TextInput component that has an opacity animation on click (exactly like TouchableOpacity changes opacity on click).
I tried wrapping the TextInput inside TouchableOpacity, but it doesn't work since the touchable component surrounds text input. Is there a standard React Native or StyleSheet way of doing this or would I have to manually create an animation to mimic that effect?
Simply wrap your TextInput in a View element and animate the View's opacity color from the onFocs event of the TextInput. TextInput doesn't have the opacity attribute, therefore the additional view is required for your goal.
Following code may give you an idea how to solve it. Have to admit, that I haven't tested the code, it may contain some error, so use it carefully.
// First create an animated value to use for the view's opacity.
const textInputAnimOpacity = useRef(new Animated.Value(1.0)).current;
// create a method to set the opacity back to 1.0
const showTxtInput = () => {
Animated.timing(textInputAnimOpacity, {
toValue: 1.0, // value to reach
duration: 250 // time in ms
}).start();
};
// this animated method differs from the first one by (obviously the value 0.7)
//and the callback part that goes into the start()-method
const blurTxtInput = () => {
Animated.timing(textInputAnimOpacity, {
toValue: 0.7, // value to reach
duration: 250 // time in ms
}).start(({ finished }) => {
showTxtInput(); // Callback after finish, setting opacity back to 1.0
});
};
/*Set the opacity to the value, we want to animate*/
<View style={{opacity:textInputAnimOpacity}}>
/* call blurTxtInput to set the value to 0.7 and again to 1.0 once it reaches 0.7*/
<TextInput onPressIn={blurTxtInput} placeholder='PlaceholderText'>
</TextInput>
</View>
If you just want to set opacity, make your styles change using the onPressIn and onPressOut props:
const [pressed, setPressed] = useState(false);
// in render
<TextInput
onPressIn={() => setPressed(true)}
onPressOut={() => setPressed(false)}
style={pressed ? styles.textInputPressed : styles.textInput}
// ...
/>
If you need the changes to animate, you can do that with the built-in RN Animated component or react-native-reanimated, using the same props to trigger the animations.

Is it possible to wait for a component to render? React Testing Library/Jest

I have a component. It has a button. Upon pressing the button, I am changing the style of the button text (color) using setState function. When I am testing the changed component, the test is failing because the change happens asynchronously. I want to do something as is given here (https://testing-library.com/docs/dom-testing-library/api-async/)
const button = screen.getByRole('button', { name: 'Click Me' })
fireEvent.click(button)
await screen.findByText('Clicked once')
fireEvent.click(button)
await screen.findByText('Clicked twice')
But rather than waiting for the text to change. I want to wait for the text color to change. Thanks
This is the code for my button
<Button onPress = {() => {this.setState({state : 1});}}>
<Text style = {style}>Button Text</Text>
</Button>
So when this button is pressed. state is set to 1. And in render :
if(this.state.state === 1) style = style1
else style = style2;
But it can be seen from logs that render is called after the test checks for the styles. So How can I wait for the render to complete before checking if the font color has been changed?
Here is the testing code
test('The button text style changes after press', () => {
const {getByText} = render(<Component/>);
fireEvent.press(getByText('button'));
expect(getByText('button')).toHaveStyle({
color : '#ffffff'
});
})
It looks like you have a custom button, not a native button. I'm guessing your component is something like this:
import React from "react";
import {Text, TouchableOpacity} from "react-native";
const Button = ({pressHandler, children}) => (
<TouchableOpacity onPress={pressHandler}>
{children}
</TouchableOpacity>
);
const ColorChangingButton = ({text}) => {
const [color, setColor] = React.useState("red");
const toggleColor = () => setTimeout(() =>
setColor(color === "green" ? "red" : "green"), 1000
);
return (
<Button pressHandler={toggleColor}>
<Text style={{color}}>{text}</Text>
</Button>
);
};
export default ColorChangingButton;
If so, you can test it with waitFor as described here:
import React from "react";
import {
fireEvent,
render,
waitFor,
} from "#testing-library/react-native";
import ColorChangingButton from "../src/components/ColorChangingButton";
it("should change the button's text color", async () => {
const text = "foobar";
const {getByText} = render(<ColorChangingButton text={text} />);
fireEvent.press(getByText(text));
await waitFor(() => {
expect(getByText(text)).toHaveStyle({color: "green"});
});
});
For a native button which has rigid semantics for changing colors and doesn't accept children, instead using title="foo", a call to debug() shows that it expands to a few nested elements. You can use
const text = within(getByRole("button")).getByText(/./);
expect(text).toHaveStyle({color: "green"});
inside the waitFor callback to dip into the button's text child and wait for it to have the desired color.
I used the same packages/versions for this post as shown in React Testing Library: Test if Elements have been mapped/rendered.
You can try
<Text style = {this.state.state === 1 ? style1 : style2}>Button Text</Text>
This will consequently lead to the style being defined all time. So you don't have to wait for the setState to complete.
Edit
You can use the callback provided by setState function to perform your tests for styles.
this.setState({
state : 1
} , () => {
//this is called only after the state is changed
//perform your test here
})

How to use refreshControl on a ScrollView but prevent dragging direction?

I have content that I want to "refresh" with a RefreshControl but I do not want the content to scroll upwards. I was originally under the impression that setting bounces=false and scrollEnabled=true would achieve this.
How about you set a key to the Component, then update it when you want to refresh it.
When a key changes, React see it as a new component. As a result, it will literally refresh the component.
export default () => {
const [refreshKey, setRefreshKey] = React.useState('randomstring');
// call me when refresh required
const refresh = () => {
setRefreshKey(refreshKey + 'randomstring');
};
return (
<ScrollView key={refreshKey}>
{/* Some Awesome Contents */}
</ScrollView>
);
};

Adjust the scrollview height dynamically at runtime

I am developing a react-native project.
I have a ScrollView in MyComponent, the content of ScrollView consists of :
a MySubComponent,
a Text
a Image component
All the content/data of above components have dynamic length or height. So, I would like adjust the content height of my ScrollView on the fly at runtime.
To achieve that my plan is to disable automaticallyAdjustContentInsets of the ScrollView as you can see from my below code snippet. Then, have a state variable to hold the latest value of contentInsetBottom. But I am not sure how can I calculate the height of child components so that I can call setContentInsetBottom(totalHeight) to update the content height of my ScrollView .
(I am pretty sure my plan will work if I know how to calculate the height of each child component of my ScrollView.)
Anyone can guide me a bit?
const MyComponent = ({myText, image}) => {
// I have a state of the contentInsetBottom for the ScrollView
const [contentInsetBottom, setContentInsetBottom] = useState(0);
// how can I get the height of each children component, sum them up & call setContentInsetBottom(totalHeight) here ?
return (
<ScrollView
automaticallyAdjustContentInsets={false}
contentInset={{top: 0, bottom: contentInsetBottom}}
style={styles.scrollView}
contentContainerStyle={styles.contentContainer}>
<MySubComponent />
<Text>{myText}</Text>
<Image source={{uri: image?.uri}}>
</ScrollView>)
}
Wrap all content inside the <ScrollView> in a <View>. Then use onLayout to get the height of that parent View.
const handleScrollContentLayout = (e) => {
const { height } = e.nativeEvent.layout
setScrollLayoutHeight(height)
}
...
<View onLayout={handleScrollContentLayout}>
{ /* scrollView content... */ }
</View>
Then you can use the scrollLayoutHeight as per your needs to set the height at runtime.

Get current scroll position of FlatList

I have this code:
<FlatList
ref={(x) => (this.flatList = x)}
data={players}
style={this.props.style}
/>
What I need is to save the current scroll position of the FlatList when the user e.g. navigates away.
So something like this.flatList.getCurrentScrollPosition().
Is there such a thing?
FlatList inherits all of ScrollView props. Therefore you can use onScroll.
onScroll = (event) => {
const scrollOffset = event.nativeEvent.contentOffset.y
}