Why does Switch in ReactNative show toggle animation although its value prop is not changed? (iOS) - react-native

I am experiencing some behavior of a ReactNative Switch that I cannot explain.
I have a very simple switch (see code below). It has a fixed value prop and its onValueChange-callback is only a log. As a switch is a controlled component, I thought the UI is only rerendered when the value prop changes.
Despite my assumptions the switch shows a quick animation of toggling to false and then jumps back to true. Why is that happening? Does it have to do with the iOS specific component that is used by ReactNative?
import React from "react";
import { Switch, View } from "react-native";
export const SwitchCell = () => {
return (
<View>
<Switch onValueChange={() => console.log("Test")} value={true} />
</View>
);
};

If you want to block the event there is a prop for switch called "onChange" where as parameter you will receive the event and the new value, you can execute a check to decide if will be necessary to set the new property o if it won't change.
In case you doesn't want to change the value of switch you have to call the methods "preventDefault()"

Related

How to unmount screen on blur in a StackNavigator?

Im using React Navigation x5, I have StackNavigator that's loading bunch of screens. I want to unmount one particular screen component on blur to get the same result of using unmountOnBlur when using a DrawerNavigator.
How can I achieve that?
There are currently three ways to do it since 5.x. You can either
use useFocusEffect hook to trigger an action (recommended), or
use useIsFocused hook, or
listen to the focus event
Example of useFocusEffect
import { useFocusEffect } from '#react-navigation/native';
function Sample(props) {
const [shouldHide, setShouldHide] = React.useState(false);
useFocusEffect(() => {
setShouldHide(false)
return () => {
setShouldHide(true)
}
});
return shouldHide ? null : <InnerComponent {...props} />;
}
Example of useIsFocused hook
import * as React from 'react';
import { Text } from 'react-native';
import { useIsFocused } from '#react-navigation/native';
function Profile() {
// This hook returns `true` if the screen is focused, `false` otherwise
const isFocused = useIsFocused();
return {isFocused ? <Text>Inner component</Text> : null};
}
Note:
Using this hook component may introduce unnecessary component re-renders as a screen comes in and out of focus. ...
Hence we recommend to use this hook only if you need to trigger a re-render.
More information can be found in the documentation Call a function when focused screen changes
Besides that, stack navigators come with a prop called detachInactiveScreens which is enabled by default. Link to the documentation
detachInactiveScreens
Boolean used to indicate whether inactive screens should be detached from the view hierarchy to save memory. Make sure to call enableScreens from react-native-screens to make it work. Defaults to true.
(The instruction on enabling React Native Screens can be found at https://reactnavigation.org/docs/react-native-screens/#setup-when-you-are-using-expo)
Inside the stack navigator's options object, there is also a detachPreviousScreen prop to customise the behaviour of certain screens. This option is usually enabled as well.
detachPreviousScreen
Boolean used to indicate whether to detach the previous screen from the view hierarchy to save memory. Set it to false if you need the previous screen to be seen through the active screen. Only applicable if detachInactiveScreens isn't set to false. Defaults to false for the last screen when mode='modal', otherwise true.

useFocusEffect hook is invoked even screen is in background when update global state on the screen in foreground

I am developing react-native project. This question is mainly about the useFocusEffect hook behaviour in my project.
I have several screens. There is a share data & each screen can update the data. So, I decided to use context to host the data via useState hook. When one screen setMyData(), the up-to-date data is available to all screens.
My data context to host shared data via useState hook:
import React, {useState} from 'react';
const MyDataContext = React.createContext();
export const MyDataProvider = ({children}) => {
const [myData, setMyData] = useState([]);
...
return (
<MyDataContext.Provider
value={{
myData,
setMyData,
}}>
{children}
</MyDataContext.Provider>
);
}
export default MyDataContext;
(My App component is wrapped by the MyDataContext.Provider, I just don't show that part here since it is not the point of my question)
In each of my screen components, I need to process my data once when the screen is on the foreground (visible), and update the global state with updated data. I use useFocusEffect hook. Something like below:
import {useFocusEffect} from '#react-navigation/native';
import MyDataContext from '../context/MyDataContext';
const MyScreenOne = ({navigation})=> {
const {myData, setMyData} = useContext(MyDataContext);
useFocusEffect(() => {
console.log('################# Screen one is FOCUSED! ');
const updatedData = processData([...myData]);
// set my data
setMyData(updatedData);
return () => {
console.log('Screen one was unfocused');
};
}, []);//empty array to force code in hook only run once
return (<View>
<MyButton onPress={()=> navigation.navigate("MyScreenTwo")}>
...
</View>
)
}
...
As you can see I have a button which navigates user to another screen. The other screen has the same structure of having that useFocusEffect hook in which it processes & updates data of global state.
When I run my app, the following happens to me:
At first MyScreenOne is launched. useFocusEffect is invoked, data is processed and updated and set to global state via the setMyData()
Since setMyData() is called in above step, the re-rendering happens, at this time, the useFocusEffect of MyScreenOne is not invoked again, which is good and expected.
Now I press the button which navigates to MyScreenTwo. Same process happens on MyScreenTwo: useFocusEffect is invoked, data is processed and updated and set to global state via the setMyData(). NOTICE: MyScreenOne is now not visible and in background stack/memory.
Since in above step the global state is updated by the second screen, re-rendering happens again to all screens in memory. This time surprisingly the useFocusEffect of MyScreenOne is invoked again, because of that the global state is updated again by the hook in MyScreenOne again, and re-rendering happens again to screens in memory.
As you can imaging , endless re-rendering happens now due to in above step the background screen MyScreenOne's useFocusEffect is invoked surprisingly.
Isn't useFocusEffect supposed to be run only when screen is visible? Why the useFocusEffect hook of background screen is also invoked when re-rendering happens? How to achieve what I need to only run the process data & update data once on each screen when the screen is visible in the meantime the data can be shared by all screens?
useFocusEffect doesn't take a dependency array. You need to pass useCallback as mentioned in the docs:
useFocusEffect(
React.useCallback(() => {
// whatever
}, [])
);
https://reactnavigation.org/docs/use-focus-effect/

Status bar doesnt respect 'barStyle' property

In React Native, using the following:
<StatusBar backgroundColor={config.colors.backgroundGray} barStyle="dark-content" />
works well. However when navigating to a different screen, even though the above is the only instance of StatusBar used in the entire app, the status bar style turns to what essentially is "light-content" on its own. Rendering the StatusBar component deeper in again seems to yield no results.
The backgroundColor is controllable however. Any ideas?
You can apply Statusbar's own function to App.js.
App.js
import { StatusBar } from 'react-native';
StatusBar.setBarStyle('dark-content', true);
static setBarStyle(style: StatusBarStyle, [animated]: boolean)

React Native Flip Card default clickable false and make true on button click

I am working on React Native Project, I am using react-native-flip-card this component.
My requirement is to make clickable false on initial start and on click of button make clickable true for the flip cards.
In the case you want to modify a behaviour inside a Component, the State Component seems like the things you need.
You can set a state property such as :
this.state = {
isClickable: false,
}
by default in your Component Constructor.
And then assign this value to your FlipCard component, such as :
<FlipCard
*Your other properties*
clickable={this.state.isClickable}
>
Finally, just update your state property when another event such as a button click happens :
<Button
onPress={() => this.setState({isClickable: true})}
title="Make Flipcard clickable"
color="#841584"
/>
That's it ! Do not hesitate to ask more question if something isn't clear enough.

React Native onPress being called automatically

I am having trouble with react-native onPress Feature. The onPress should only work when it is actually been triggered by a touch event (i suppose) , that is when i press the button on the screen. But it seems the onPress gets triggered itself when the render function is called. When i try to press manually, it doesn't work.
import React, { Component } from 'react';
import { PropTypes, Text, View ,Alert } from 'react-native';
import { Button } from 'react-native-material-design';
export default class Home extends Component {
render() {
return (
<View style={{flex:1}}>
<Button value="Contacts" raised={true} onPress={this.handleRoute('x')} />
<Button value="Contacts" raised={true} onPress={this.handleRoute('y')} />
<Button value="Contacts" raised={true} onPress={this.handleRoute('z')} />
</View>
);
}
handleRoute(route){
alert(route) // >> x , y, z
}
}
module.exports = Home;
What am i missing ? Is there something wrong with the way i have assigned or this is some bug ?
Any suggestion is highly appreciated.
Video
try to change
onPress={this.handleRoute('x')} // in this case handleRoute function is called as soon as render happen
to
onPress={() => this.handleRoute.bind('x')} //in this case handleRoute doesn't called as soon as render happen
You can change to this:
onPress={this.handleRoute.bind(this, 'x')}
or this:
onPress={() => this.handleRoute('x')}
The reason is that onPress takes a function as an argument. In your code, you are calling the function and returning the result immediately (when render is called) rather than referencing the function for React to call later on the press event.
The reason you need the bind(this) is because the function loses it's bound instance when you just do (this.handleRoute) and you have to tell it which this to use. The bind function takes the other arguments to call on the function later. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind for more descriptive info on bind.
There is another way in which you can bind in the constructor. You can read about ways to handle this in React here: https://facebook.github.io/react/docs/handling-events.html
onPress={this.handleevent.bind(this, 'A')}
or use this:
onPress={() => this.handleevent('B')}
Change
onPress={this.handleRoute('x')}
to
onPress={()=>this.handleRoute('x')}
Otherwise, the function gets invoked as soon as the render method gets called.
The reason for such behaviour is on every render, reference to the function is created.
So, to avoid that, use bind function OR arrow function to call on onPress