Make react-native component blink at regular time interval - react-native

I am trying to make a component "blink" on my page. I was thinking about setting a visible: true state in my componentWillMount method and then put a timeout of 1s in componentDidUpdate to set state to the "opposite" of the previous state. As I see it the component lifecycle looks like this :
sets state to visible to true (componentWillMount that runs only once and is not triggering a rerender)
enters componentdidUpdate
waits 1s
hides component (setstate to visible false)
enters componentDidUpdate
waits 1s
shows component (setstate to visible true)
However my component is blinking but the intervals of hide and show are not regular, they change and dont seem to follow the 1s logic
Here's my component code :
class ResumeChronoButton extends Component {
componentWillMount(){
console.log('in componentWillMount')
this.setState({visible: true})
}
componentDidUpdate(){
console.log('in componentDidUpdate')
setTimeout(() =>this.setState({visible: !this.state.visible}), 1000)
}
// componentWillUnmount(){
// clearInterval(this.interval)
// }
render(){
const { textStyle } = styles;
if (this.state.visible){
return (
<TouchableOpacity onPress={this.props.onPress}>
<Pause style={{height: 50, width: 50}}/>
</TouchableOpacity>
);
}
else {
return (
<View style={{height: 50, width: 50}}>
</View>
)
}
}
};
How can I make my component blink at regular time interval.

The following works for me
componentDidMount = () => {
this.interval = setInterval(() => {
this.setState((state, props) => {
return {
visible: !state.visible,
};
});
}, 1000);
};
componentWillUnmount = () => {
clearInterval(this.interval);
};
and then your render can just check this.state.visible to determine if it needs to show or not.
alternatively you could change the setState to
this.setState({visible: !this.state.visible})

Most likely because you are using the state and timeouts. State is set asynchronously and, for this reason, it may take different amounts of time to change the value depending on how many resources you are using.
To achieve the effect you want I would recommendo you to use the Animation framework from React Native. Check the docs.

just use
setInterval(()=>{//setstate here},time_in_ms)

Related

useState function seems to block Animated.timing event?

I've created a "twitter style" button that when pressed opens up a sub-menu of items that can be selected/"tweeted" about.
The button is simple in that when pressed, it triggers a function with Animated events:
const toggleOpen = () => {
if (this._open) {
Animated.timing(animState.animation, {
toValue: 0,
duration: 300,
}).start();
} else {
Animated.timing(animState.animation, {
toValue: 1,
duration: 300,
}).start(); // putting '() => setFirstInteraction(true)' here causes RenderItems to disappear after the animation duration, until next onPress event.
}
this._open = !this._open;
};
and here's the button that calls this function:
<TouchableWithoutFeedback
onPress={() => {
toggleOpen();
// setFirstInteraction(true); // this works here, but the button doesn't toggleOpen until the 3rd + attempt.
}}>
<Animated.View style={[
styles.button,
styles.buttonActiveBg,
]}>
<Image
style={styles.icon}
source={require('./assets/snack-icon.png')}
/>
</Animated.View>
</TouchableWithoutFeedback>
I need to add a second useState function that is called at the same time as toggleOpen();. You can see my notes above regarding the problems I'm facing when using the setFirstInteraction(true) useState function I'm referring to.
Logically this should work, but for some reason when I add the setFirstInteraction(true) it seems to block the toggleOpen() function. If you persist and press the button a few times, eventually the toggleOpen() will work exactly as expected. My question is, why does this blocking type of action happen?
You can reproduce the issue in my snack: https://snack.expo.dev/#dazzerr/topicactionbutton-demo . Please use a device. The web preview presents no issues, but on both iOS and Android the issue is present. Line 191 is where you'll see the setFirstInteraction(true) instance.
Your animatedValue isn't stable. This causes it to be recreated on each state change. It is advised to useRef instead (though, useMemo would do the trick here as well).
const animState = useRef(new Animated.Value(0)).current;
Your toggleOpen function can also be simplified. In fact, you only need a single state to handle what you want and react on it in a useEffect to trigger the animations that you have implemented.
I have called this state isOpen and I have removed all other states. The toggleOpen function just toggles this state.
const [isOpen, setIsOpen] = useState(false)
const toggleOpen = () => {
setIsOpen(prev => !prev)
}
In the useEffect we react on state changes and trigger the correct animations.
const animState = useRef(new Animated.Value(0)).current;
useEffect(() => {
Axios.get('https://www.getfretwise.com/wp-json/buddyboss/v1/forums')
.then(({ data }) => setData(data))
.catch((error) => console.error(error));
}, []);
useEffect(() => {
Animated.timing(animState, {
toValue: isOpen ? 1 : 0,
duration: 300,
useNativeDriver: true,
}).start();
}, [isOpen, animState])
I have adapted your snack. Here is a working version.
Remarks: Of course, you still need for your data to be fetched from your API. The opacity change of the button is still the same and it remains disabled until the data has been fetched.

React Native - component callback animates a view first time but not on subsequent calls

I have an animation which is triggered via a callback function sent to a component in render(). The animation is just some text moving from outside the right edge of the screen across the screen and off the left side. The first time the callback is triggered it works perfectly, but on subsequent callbacks nothing can be seen and there are no errors. Relevant code below:
import { Animated, Easing....} from 'react-native';
class Play extends Component {
constructor() {
super();
this.state = {currSectName:"Intro",
sectNameXcord: new Animated.Value(Dimensions.get('window').width),....}
}
sectNameTraverse = (newSect) =>{
this.state.currSectName = newSect;
this.state.sectNameXcord = new Animated.Value(Dimensions.get('window').width);
//run the animation
Animated.timing(
this.state.sectNameXcord,
{
toValue: -320,
duration: 3000, // the duration of the animation
easing: Easing.linear, // the style of animation
useNativeDriver: true
}
).start((res)=>{
console.log(res);
});
}
render() {
return (
<>
<Animated.View style={{position: 'absolute',top:100,
transform: [{translateX: this.state.sectNameXcord }]}}>
<Text style={{ color:"white",opacity:0.5,fontStyle: 'italic',fontWeight: 'bold',fontSize:99}}>
{this.state.currSectName}
</Text>
</Animated.View>
<Player
sectionChange={(newSect) => {this.sectNameTraverse(newSect)}}
/>
}
On subsequent callbacks the following is true:
the state variables are updated and correct for the next animation
the callback of Animated.timing().start() shows {"finished": true} after the duration period as if it has executed correctly BUT nothing shows up on the screen
no errors are thrown
I'm guessing it may have something to do with instances/binding but I'm not good enough
with React Native to fix it. Been stuck on it for 2 days and could really do with some help.
Thanks a lot.
You should not mutate the state. you should be using 'setState' to update the state.
sectNameTraverse = (newSect) => {
this.setState(
{
currSectName:newSect,
sectNameXcord: new Animated.Value(Dimensions.get('window').width)
},
() => {
//run the animation
Animated.timing(this.state.sectNameXcord, {
toValue: -320,
duration: 3000, // the duration of the animation
easing: Easing.linear, // the style of animation
useNativeDriver: true,
}).start((res) => {
console.log(res);
});
}
);
};
here is the snack i created.

Changing state in ComponentWillMount goes to an infinite loop

In my react native app i'm changing a style at a fixed time from 7 to 7:30pm. I am changing the state for that in ComponentWillMount. But whenever i go to that component,it starts calling that state again and again and doesn't even stop when i go to a different component. I want to stop this infinite loop of calling itself.
Here's the code:
import { withNavigation } from "react-navigation";
class Third extends Component {
constructor(props) {
super(props);
this.state = {
toggle: 0,
live: false
}
}
componentWillMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener("didFocus", () => {
this.changeLang()
});
var today = new Date()
var time = today.getHours()
console.log(today.getMinutes())
var weekDay = today.getDay()
if ((time >= 19) && (time <= 20 ) && (weekDay === 0 ||3 ||6)){
if(today.getMinutes()<=30){
this.setState({ live: true })
}
}
}
async changeLang() {
try {
const value = await AsyncStorage.getItem('toggle')
this.setState({ toggle: JSON.parse(value) })
} catch (e) {
// error reading value
}
}
render() {
const state = this.state;
console.log('live', this.state.live)
this.changeLang()
return (
<Container style={{ backgroundColor: '#fff' }}>
<Content>
<Left></Left>
<Body></Body>
<Right>{(this.state.live === true) ? <Icon name='football'/>: <Icon name='refresh'/>}</Right>
</View>
</Card>
</View>
</Content>
</Container>
);
}
}
export default withNavigation(Third)
Here this.state.live keeps on giving consoles and doesn't stop. What can be done here to resolve this?
Your problem is with this in render function,
this.changeLang()
Every time you call this.setState your component will re-render, and when component re-render your render function will get called.
So the sequence causing infinite loop is,
Component mounts => in render function you call this.changeLang() => in changeLog function you are calling this.setState => render function get called which again executes this.changeLang() => in changeLog function you are calling this.setState ... so on.
In this way you end up with infinite loop.
Just remove this.changeLang() from render function as you are already calling the same in componentWillMount.
Calling setState here makes your component a contender for producing infinite loops. remove this.changeLang() make it work

Animated API's createAnimatedComponent makes FlatList's ref undefined in react-native

I'm trying to use scrollToIndex function of FlatList on react-native. However, when I switch the FlatList to createAnimatedComponent(FlatList), its ref becomes undefined.
Is there a way to keep FlatList's ref when I use createAnimatedComponent?
Thank you for your concern.
currently createAnimatedComponent exposes a method called getNode() that should work for getting the ref to the underlying component.
Here are 2 examples, one with the old refs and one with the new
// old ref style
class DemoComp extends Component {
componentDidMount() {
// setTimeout is needed for scrollToOffset ¯\_(ツ)_/¯
setTimeout(() => {
this.listRef.getNode().scrollToOffset({ offset: 100, animated: true });
}, 0);
}
render() {
return <Animated.FlatList ref={r => { this.listRef = r; }} {...otherProps} />;
}
}
// new ref style
class DemoComp extends Component {
listRef = React.createRef();
componentDidMount() {
// setTimeout is needed for scrollToOffset ¯\_(ツ)_/¯
setTimeout(() => {
this.listRef.current.getNode().scrollToOffset({ offset: 100, animated: true });
}, 0);
}
render() {
return <Animated.FlatList ref={this.listRef} {...otherProps} />;
}
}
Eventually, in the future createAnimatedComponent will switch to "forwarded refs" which will only work with the new style. But that day is a long way away because of all the libraries that depend on the old style.
PS. If you're reading this in the distant future, you can check on the status of forwarded refs in createAnimatedComponent here: https://github.com/facebook/react-native/issues/19650

components get render even routed out on componentWillMount method?

class LoginScreen extends Component {
componentWillMount() {
const { currentUser } = firebase.auth();
console.log(currentUser);
if (currentUser) {
Actions.mainScreen();
}
}
render(){...}
}
Basically the idea very simple, once user comes to LoginScreen, it checks if the user existed, if yes, instead of render this component, redirect to another component instead.
The problem with the above code is that, LoginScreen still gets to render for 1 to 2 seconds before render mainScreen. Wondering if this is the way to handle such routing conditionally in React-Native-Router-Flux?
To solve this problem. declare a state
state = {
isChecking: true
}
in your componentWillMount
componentWillMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
Actions.mainScreen();
} else {
this.setState({ isChecking: false });
}
});
}
or better still with the async/await syntax
async componentWillMount() {
let user = await firebase.auth().onAuthStateChanged;
user ? Actions.mainScreen() : this.setState({ isChecking: false });
In your render method first, check when you are still checking or not. If so just render an activityIndicator. At the very very top of your render method check whether you are still checking or not.
render() {
if (this.state.isChecking) {
return (
<View
style={{ justifyContent: "center", alignItems: "center", flex:1 }}
>
<ActivityIndicator size={"large"} />
</View>
);
// rest of your loginScreen components
}