Get state in React Native IOS Tab Bar - ios

I'm a bit new to react-native, so I might not have understood this while reading the docs but. I have a tab bar in an ios app with 2 tabs.
Currently the view code for one of the tabs is in the same file as the page that loads the the Tab bar (I call this HomeTab). The second tab (call Tab2) which contains the view code for the second tab is in a separate .js page and is loaded into HomeTab.
I'm having trouble passing information in HomeTab's state to Tab2, and do not want to write all of Tab2 into HomeTab like I did for Tab1 because I will get a really big file.
The code structure is a bit like this:
{View Code for Tab 1 here}
<TabBarIOS selectedTab={this.state.selectedTab}>
<TabBarIOS.Item
selected={this.state.selectedTab === 'Tab1'}
title = "Tab1"
icon = {require('image!Tab1')}
onPress={() => {
this.setState({
selectedTab: 'Tab1'
}
);
}}>
// Call Tab1 View here
</TabBarIOS.Item>
<TabBarIOS.Item
selected={this.state.selectedTab === 'Tab2'}
title = "Tab2"
icon = {require('image!Tab2')}
onPress={() => {
this.setState({
selectedTab: 'Tab2'
}
);
}}>
<Tab2/>
</TabBarIOS.Item>
Intuitively I feel like all I am doing is putting code in a separate file, so both tabs should have the same state, but when I print out anything from my state it is undefined. Any advice would be appreciated! Thanks.

I was fairly ignorant of the capabilities of javascript. I was able to pass props just by including it in the call to Tab2 like this
<Tab2 propName = {some information}/>

Related

antd Dropdown: Stop Propagation of Click Trigger

I'm looking for a way to stopPropagation() the click event of an antd Dropdown component with a sole 'click' trigger.
Here's an example of a Dropdown where the open/close click event is propagated to the parent Collapse header, which causes the Panel to collapse/expand, which is undesired:
https://codesandbox.io/s/stop-propagation-dropdown-trigger-click-cg005
Screenshot
Note that click events of the button and the menu items can be stopped from propagating in the respective event handlers.
What I'm looking for is how to do the same for the dropdown click events (click on the "⋯" part), which doesn't seem to be easily accessible.
Providing the onClick={e => e.stopPropagation()} callback to the immediate child of <Dropdown trigger={["click"]}> should solve the issue. Tt worked for me:
return (
<Dropdown trigger={["click"]} overlay={actionsMenu}>
<div onClick={e => e.stopPropagation()} className="action-button-container">
<MoreOutlined />
</div>
</Dropdown>
)
#shimikano, as for your particular case, you can override the buttons by using the buttonsRender prop from the <Dropdown.Button> component. buttonsRender is a function that receives an array with 2 ReactNode elements inside, first one is the actual button (named Dropdown in your case), and the second one is more icon which you can override with the same icon to which you have access and can provide the onClick callback and call event.stopPropagation() inside
buttonsRender={buttons => {
buttons[1] = <MoreOutlined onClick={e => e.stopPropagation()} />;
return buttons;
}}
Here is how the collapse header would look like:
const header = (
<Space wrap>
<Dropdown.Button
size="small"
trigger={["click"]}
onClick={handleButtonClick}
overlay={menu}
buttonsRender={buttons => {
buttons[1] = <MoreOutlined onClick={e => e.stopPropagation()} />;
return buttons;
}}
>
Dropdown
</Dropdown.Button>
</Space>
);

Prevent FlatList re-rendering causing performance problems

I'm building a React Native App using Hooks and ES6.
The Home screen fetches some data from API and display it as image galleries.
The Home screen is the parent component which includes a slideshow gallery and a Flatlist as children.
1) Slideshow - image gallery autoplay that run some operations (fetching URL in case user clicks on them) on each image iteration (every 3 secs) - This causes the re-render on the entire parent components and its children but it renders every 3 secs as supposed
2) Flatlist - just a simple Flatlist that takes an array of images - this should only rendered once when the parent component is initially loaded first
The problem
Every 3 seconds a new image is displayed in the slideshow, a function runs to fetch some props for the image displayed, the parent component is re-rendered but there is no need for Flatlist in the second gallery to run again since the array of images is the same as initially loaded (not changed)
This is my Parent code
const getTopTenMovies = (state) => state.moviescreen.popularmovies //fetching data from Redux
const upcomingMovies = (state) => state.moviescreen.upcomingmovies
const MoviesScreen = (props) => {
const topTenMovies = useSelector(getTopTenMovies);
const [imageIndex, setImageIndex] = useState("");
/* useEffect below runs every time imageIndex changes (a new image is displayed every 3 secs) */
useEffect(() => {
setCarouselMovieId(selectedImageItem.id);
setCarouselMovieTitle(selectedImageItem.title);
}, [imageIndex]);
/* user clicks on an image and is taken to another screen
const fetchMovieHandler = async (movieId, movieTitle) => {
props.navigation.navigate({
routeName: "MovieDetail",
params: {
assetId: movieId,
assetName: movieTitle,
},
});
};
return (
<ImageGallery
data={topTenMovies}
currentImage=setImageIndex(index)
...
/>
<View style={styles.containerRow}>
<FlatList
horizontal={true}
data={upcomingMovies}
initialNumToRender={5}
windowSize={10}
removeClippedSubviews={true}
keyExtractor={(item) => item.id.toString()}
renderItem={(itemData) => (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
onAssetSelection={fetchMovieHandler}
/>
)}
/>
</View>
</View>
)
}
The component renders the individual images within the Flatlist and passes the parameters (movieId, movieTitle) to the fetchMovieHandler function.
HomeItem code below...
class HomeItem extends React.PureComponent {
render() {
const assetHandler = async () => {
this.props.onAssetSelection(this.props.id, this.props.title);
};
console.log("HomeItem is called");
return (
<TouchableOpacity onPress={assetHandler}>
<View>
<Image
key={this.props.id}
source={{
uri: `https://*******${this.props.poster}`,
}}
/>
</View>
</TouchableOpacity>
);
}
}
export default React.memo(HomeItem);
Every 3 seconds the HomeItem is called and I see 10 log entries (one for each image and render - upcomingMovies has 10 images). From the UI everything looks fines since the images dont seems to change probably because I've defined the HomeItem as PureComponent and using React.memo(HomeItem) but it is causing performance issues since I'm getting the following error:
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc
I tried to include the Flatlist inside the HomeItem component but the problem persists.
Following further research and thanks to #landorid suggestion I figured out that I need control the renderItem of the FlatList by declaring it in a separate function with a useCallback hook and dependency on my data source.
Tried to include Flatlist in a separate PureComponent my the re-rendering kept happening.
The ideal solution is to change this code
renderItem={(itemData) => (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
onAssetSelection={fetchMovieHandler}
/>
)}
to something like this
.....
const renderRow = ({ itemData }) => {
return (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
title={itemData.item.title}
onAssetSelection={fetchMovieHandler}
/>
);
};
keyExtractor = (item) => item.id.toString();
return (
<FlatList
horizontal={true}
data={upcomingMovies}
initialNumToRender={5}
windowSize={10}
removeClippedSubviews={true}
keyExtractor={keyExtractor}
renderItem={renderRow}
/>
and then wrapping the renderRow() function in a useCallback hook.
The problem is that my data source data={upcomingMovies} is an array of objects with 2 levels and I need to use itemData.item.title to retrieve the title value for example.
While it works in my current renderItem settings it won't work when I use it in an external function as in
const renderRow = ({ itemData }) => {
return (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
title={itemData.item.title}
onAssetSelection={fetchMovieHandler}
/>
);
};
I simply get, "item" variable not available
Any suggestion on how to modify the function?
If I were you I would change the following:
Don't place inline functions in FlatList (keyExtractor, renderItem)! Place it to separated function.
Use simple Component extension instead of PureComponent and compare your props with `shouldComponentUpdate. (I think the passed function called onAssetSelection causes the re-render. Check it.)
You don't need to use React.memo because it is for functional components.

How to implement a "goBack" Function to return to the initial navigator?

This is my first post, so sorry for any errors.
I'm trying to make a series of nested navigators, starting from a Switch navigator, then going to a Drawer Navigator, that for each of it's sections, it calls a different Stack Navigator, like this:
SwitchNavigator
DrawerNavigator
MapStackNavigator
Map Screen
ProfileStackNavigator
Profile Screen
The initial route for the Drawer Nav is the Stack Nav which calls a Map Screen. The problem is, if I go to the "Profile" Section of the drawer nav, which calls for the Stack Nav that opens the Profile Screen, I'm not beeing able to return to the Map Screen through an onPress() method inside the Navigation headerLeft Icon.
The react-native app is being developed for iOS only, and now as I don't have an iOS device to test on, neither a Mac to use a simulator, I'm currently testing it on my android smartphone.
As I've tested, it wouldn't be a problem if the app was meant to run on android, as the android has the built-in "back" button and it automatically goes back to the previous screen (which is set to the Map Screen inside the DrawerNav). But, as it is for iOS only, we actually need to display a "back" button on the top of the screen (so, inside the StackNav Header) for going back to the Map Screen.
My first guess was to use the navigation.navigate inside the onPress() to call another screen from the current profile StackNav, and that screen calls the first SwitchNav that I've Created to load things up, but it won't work, as it acts like a "navigation loop", and the const SwitchNavigator can't be called from the Profile StackNav.
I've tried to call the StackNav that holds the MapScreen from the onPress(), so it was: click on the back button > it calls the onPress() which calls another screen > that another screen calls the StackNav holding the MapScreen, but it nests one StackNav inside the other.
My Routes.js looks like this:
import Testemapa from './src/components/Map/index';
import MyProfile from './src/components/DrawerNavigator/MyProfile';
import Icon from 'react-native-vector-icons/Ionicons';
import IconE from 'react-native-vector-icons/Entypo';
// The StackNav that holds the Map Screen
const IvesStackNavigator = createStackNavigator ({
screenMap: Testemapa,
},
{
defaultNavigationOptions: ({navigation})=>{
return{
headerRight: (<Icon
style={{paddingRight: 10}}
onPress={() => navigation.openDrawer()}
name="md-menu" size={30}
/>)
};
}
});
// What I tried to do to call the MapScreen, but it ends up nesting it inside
const wayBackSwitchNavigator = createSwitchNavigator ({
transitionToHome: IvesStackNavigator,
});
// The StackNav that holds the Profile Screen
const myProfileStackNavigator = createStackNavigator ({
drawer_myProfile: MyProfile,
transitionPathHome: wayBackSwitchNavigator,
},
{
headerBackTitleVisible: 'true',
defaultNavigationOptions: ({navigation})=>{
return{
//This guy is the Back button, that's actually an Icon
headerLeft: (<IconE
style={{paddingLeft: 10}}
onPress={() => navigation.navigate('transitionPathHome')}
name="chevron-small-left" size={30}
/>),
};
},
});
// The Drawer which calls all of the stacks
const MainDrawerNavigator = createDrawerNavigator ({
BOOK_A_RIDE: {
screen: IvesStackNavigator,
navigationOptions: () =>
({
title: 'BOOK A RIDE'
})
},
MY_EARNINGS: {
screen: ridesStackNavigator,
navigationOptions: () =>
({
title: 'MY EARNINGS'
})
},
MY_PROFILE: {
screen: myProfileStackNavigator,
navigationOptions: () =>
({
title: 'MY PROFILE'
})
},
},
{
initialRouteName: 'BOOK_A_RIDE',
drawerOpenRoute:'DrawerOpen',
drawerPosition: 'right',
drawerBackgroundColor: 'black',
hideStatusBar: 'true',
overlayColor: 'red',
edgeWidth: 100,
contentOptions: {
inactiveBackgroundColor: 'black',
inactiveTintColor: '#fff',
activeBackgroundColor: '#4d4d4d',
activeTintColor: '#fff',
},
contentComponent: props =>
<Container style={{alignItems: 'stretch', marginLeft: 0, marginRight : 0, backgroundColor: 'black'}}>
<Header style={{height:150, marginLeft: 0, marginRight : 0, backgroundColor: 'black'}}>
<Body style={{marginLeft: 0, marginRight : 0, backgroundColor: 'black'}}>
<ImageBackground source={require('./src/assets/splash_ImageLimo.jpg')} style={{left: -10, width: 264, height:150, opacity:0.7, marginLeft: 0, marginRight : 0, paddingRight: 0}}>
</ImageBackground>
</Body>
</Header>
<Content>
<DrawerItems {...props} />
</Content>
</Container>
},
);
// The SwitchNav that is the first Navigator to run on the app
const firstSwitchNavigator = createSwitchNavigator(
{
TestDrawer: MainDrawerNavigator,
},
{
initialRouteName: 'TestDrawer',
}
);
export default createAppContainer(MySwitchNavigator);
So, when The Back button inside the MyProfileStackNavigator is pressed, It was supposed to go back to the MapScreen which is holded by the IvesStackNavigator, which is inside the mainDrawerNavigator.
I'm not allowed to let the user open the DrawerNav from the Profile Screen, so the back button is really important, as it's the only way to go back to the map.
If I try to directly call the SwitchNav or the DrawerNav by using any of them as the Screen path in MyProfileStackNavigator, I get the error that the screen that was called isn't a React component ("The component for route 'transitionToHome' must be a React component...").
If I change it to call IvesStackNavigator directly, the navigator will end up nested inside the MyProfileStackNavigator.
And, if I call the actual map page, it will display the map page inside the MyProfileStackNavigator, having all of the MyProfileStackNavigator properties (like the Back button and the absency of the button to open the drawer menu).
Any help with this would be appreciated, I'm fairly new to React-Native and it's dependencies, and I had to jump in right to coding before learning, as it is a task assigned to me by my boss, so I try to learn while producing the app.

Vaadin 8 using an image as a button. Doesn't fire click event

I'm building a Vaadin 8 app ( first one for me ). I am using the designer to generate the UI. I've added several buttons to the dashboard which should fire a function when clicked. For some reason nothing fires when the image is clicked. Below is all the code that is involved. Can anyone see what I'm doing wrong?
This is the code from the .html file:
<vaadin-horizontal-layout responsive width-full margin>
**<vaadin-image icon="theme://images/properties.png" style-name="my-image-button" responsive alt="" _id="imagePropertyInfo"></vaadin-image>**
<vaadin-image icon="theme://images/occupants.png" responsive alt="" _id="imageOccupants"></vaadin-image>
<vaadin-image icon="theme://images/vendors.png" responsive alt="" _id="imageVendors"></vaadin-image>
</vaadin-horizontal-layout>
Here is the scss
.my-image-button
{
cursor: pointer;
}
Here is the code from the Dashboard UI
public DashboardHomeView( OnCallUI onCallUI )
{
this.onCallUI = onCallUI;
// Make it disabled until a property is selected
**imagePropertyInfo.setEnabled( false );
imagePropertyInfo.setStyleName( "my-image-button" );**
fetchPropertyBasicInfo();
}
protected void fetchPropertyBasicInfo()
{
List<PropertyProfileBasic> listOfPropertyProfiles = new ArrayList<PropertyProfileBasic>( OnCallUI.myStarService.fetchAllPropertyProfileBasicInformation() );
comboBoxGeneric.setCaption( "Select a Property" );
comboBoxGeneric.setItemCaptionGenerator( aProperty -> aProperty.toString() );
comboBoxGeneric.setItems( listOfPropertyProfiles );
comboBoxGeneric.addValueChangeListener( event -> fetchOccupantBasicInfo( event ) );
comboBoxGeneric.focus();
}
protected void fetchOccupantBasicInfo( ValueChangeEvent<PropertyProfileBasic> event )
{
// Fetch all the occupants for the selected property
if( event.getValue().getPropertyNo() != null )
{
// Fetch a list of occupant basic info for the selected property
List<OccupantProfileBasic> listOfOccupantProfiles = new ArrayList<OccupantProfileBasic>( OnCallUI.myStarService.fetchOccupantProfileBasicByPropertyNo( event.getValue().getPropertyNo() ) );
// Clear the existing grid et al
gridContainer.removeAllComponents();
// Add the occupant grid
occupantGrid = new OccupantProfileBasicGrid( listOfOccupantProfiles );
// Show the grid
gridContainer.addComponents( new Label( "Occupants" ), occupantGrid );
// Set the dashboard buttons to enabled now a property is selected
**imagePropertyInfo.setEnabled( true );
// Add the property info button
imagePropertyInfo.addClickListener( e -> fetchPropertyInformation() );**
}
}
protected void fetchPropertyInformation()
{
Notification.show( "Yo!", "You clicked!", Notification.Type.HUMANIZED_MESSAGE );
}
I assume you are using GridLayout. I am recommending another approach. Use Button, and set the button style to be borderless (apparently you want something like that. The icon of the button can be image from your theme, using ThemeResource. "Pseudo code" is something like this:
ThemeResource icon = new ThemeResource("/images/properties.png");
Button imagePropertyInfo = new Button(icon);
imagePropertyInfo.setStyleName(ValoTheme.BUTTON_BORDERLESS);
imagePropertyInfo.addClickListener( e -> fetchPropertyInformation() );
Note also, JavaDoc of Image component says.
"public Registration addClickListener(MouseEvents.ClickListener listener)
Add a click listener to the component. The listener is called whenever the user clicks inside the component. Depending on the content the event may be blocked and in that case no event is fired."
I think it does not like your way of setting image with theme, without using Resource.
If you want to remove the focus highlight of the button, it should be possible via this CSS rule:
.v-button-link:after {
content: none;
}
Also it is worth of mentioning that Image is not Focusable, while Button is. This means that even that Image can have click listener, it is not reached by keyboard navigation, Button is Focusable and is reached by tabbing etc. So using Button instead of Image makes your application more accessible.

TabBarIOS UI customization

So I am looking to customize the TabBarIOS ui a little bit. Right now the TabBar has an icon field and a name field:
iconName="ios-pin"
title="Map"
Using these results in something like this (with the icon above the text):
However I am trying to get something that looks a like this (with the icon next to the text p.s sorry for the crappy image)
Does anyone have any idea how to do this? Below is my current TabBarIOS code.
Thanks!
renderScene(route, navigator) {
return (
<TabBarIOS
translucent={false}
unselectedTintColor="white"
tintColor="#f9c827"
barTintColor="#2c2c2c">
<Icon2.TabBarItemIOS
iconName="ios-pin"
title="Map"
selectedIconName="ios-pin"
selected={this.state.selectedTab === 'MapTab'}
onPress={() => {
this.setState({
selectedTab: 'MapTab',
});
}}>
{this.renderMap(route, navigator)}
</Icon2.TabBarItemIOS>
<Icon2.TabBarItemIOS
iconName="ios-list-box-outline"
title="List"
selectedIconName="ios-list-box-outline"
selected={this.state.selectedTab === 'ScrollTab'}
onPress={() => {
this.setState({
selectedTab: 'ScrollTab',
toolbar: YELLOW,
navigationBar: NavigationBarYellow,
});
}}>
{this.renderScrollView(route, 'ScrollTab')}
</Icon2.TabBarItemIOS>
</TabBarIOS>
);
}
The simplest way is to use images with text, the other one is to create container viewcontroller and use UIView instead of UITabbar.
That view should have two buttons with icons and texts that you need.

Resources