In my application I have an header component with a SearchBox and label and I have used that component in the drawer menu for a common layout and that component is appearing as a generic search component in all of my screen.My header component is like this ..
return (
<Block style={headerStyles}>
<NavBar
back={back}
title={title}
style={styles.navbar}
transparent={transparent}
right={this.renderRight()}
rightStyle={{ alignItems: "center" }}
leftStyle={{ flex: 0.3, paddingTop: 2 }}
leftIconName="navicon"
leftIconColor={white ? theme.COLORS.WHITE : theme.COLORS.ICON}
titleStyle={[
styles.title,
{ color: theme.COLORS[white ? "WHITE" : "ICON"] }
]}
onLeftPress={this.handleLeftPress}
/>
<Input
right
color="black"
style={styles.search}
placeholder="What are you looking for?"
iconContent={
<Icon
size={16}
color={theme.COLORS.MUTED}
name="magnifying-glass"
family="entypo"
/>
}
/>
</Block>
);
And inside my menu I have used it like this.
const DashboardStack = createStackNavigator(
{
Components: {
screen: DashboardScreen,
navigationOptions: ({ navigation }) => ({
header: (
<Header
title="Dashboard"
navigation={navigation}
/>
)
})
},
ProductDetail: {
screen: ProductDetailsTest
}
},
{
cardStyle: { backgroundColor: "#EEEEEE" },
transitionConfig
}
);
Now I want to call a search functionality in my Dashboard screen how I will implement this.Any reference for that >
Thanks
Utpal Maity
If I understand your question correctly, you want to use Header component's search functionality in one of your screens.One of the ways you can achieve this is, render Header component in your rootApp component(where you are initialising stackNavigator ) and have a ref assigned to it. Then, you can pass this ref as screen props to stackNavigator(rootNavigator) component. With this, all screens will have access to your Header component.
Hope this solves your query.
Refer : https://reactnavigation.org/docs/en/navigation-options.html
Refer:How screenProps can be used? and make changes according to your requirements.
Edit: I'll try to provide an example..
1.Create your AppContainer with your AppNavigator like you have done..
const ScreenStack = createAppContainer(createStackNavigator(
{
Components: {
screen: DashboardScreen,
},
ProductDetail: {
screen: ProductDetailsTest
}
},
{
cardStyle: { backgroundColor: "#EEEEEE" },
transitionConfig
}
))
2.Render ScreenStack in a Component called Root with your Header component with ref passed to it and pass Header component as screenProps to ScreenStack
class Root extends React.Component{
....
render(){
return(){
<View style={{flex:1}}>
<Header ref={this.header}></Header>
<ScreenStack
ref={this.navigator}
screenProps={{header: this.header }}
/>
</View>
}
}
}
export default Root
3.Now, in your App.js render Root
//import Root
class App extends React.Component {
render() {
return (
<Root />
);
}
}
Now, you should be able to access Header in any of the screens and use method of searching like this.props.screenProps.header.current.search()
Hope, this helps you!!
Related
How do I pass props to my screen components in TabNavigator? And with that I mean FindDestinationScreen.
I would like to somehow have a HOC that wraps <AppContainer> and passes props to my screens.
Ive tried different solutions that people has answered to similar questions here but can't get it to work..
Here is my code and this is my app.js:
import { SafeAreaView, View } from "react-native";
import { createAppContainer } from "react-navigation";
import { createMaterialBottomTabNavigator } from "react-navigation-material-bottom-tabs";
import Icon from "react-native-vector-icons/Ionicons";
import FindDestinationScreen from "./src/screens/FindDestinationScreen";
import CurrentTripScreen from "./src/screens/CurrentTripScreen";
const TabNavigator = createMaterialBottomTabNavigator(
{
FindDestinationScreen: {
screen: FindDestinationScreen,
navigationOptions: {
title: "Search",
tabBarIcon: ({ tintColor }) => (
<SafeAreaView>
<Icon
style={[{ color: tintColor }]}
size={25}
name={"ios-search"}
/>
</SafeAreaView>
)
}
},
CurrentTripScreen: {
screen: CurrentTripScreen,
navigationOptions: {
title: "Trip",
tabBarIcon: ({ tintColor }) => (
<SafeAreaView>
<Icon style={[{ color: tintColor }]} size={25} name={"ios-train"} />
</SafeAreaView>
)
}
},
{
shifting: false,
labeled: true,
initialRouteName: "FindDestinationScreen",
activeColor: "#ffffff",
inactiveColor: "#000",
barStyle: { backgroundColor: "#456990", height: 80, paddingTop: 10 }
}
);
const AppContainer = createAppContainer(TabNavigator);
export default App = () => {
return <AppContainer />;
};
What I want to happen: Let's say that if I write <AppContainer testProp="testString" />. Is it possible to retrieve this prop in the screens written in TabNavigator? I mean is it possible to access the prop if I write it in some other way?
What I have tried: I have tried the code above and also just the prop in TabNavigator like so <FindDestionationScreen testProp="testString">
This works but I dont want to write this on every screen.
If you want to use Context API, create your context, and wrap your root app component/AppStack with the provider then you can have access to all states in any of your screens/pages by using useContext hook. For example;
<ImageProvider>
<FoodProvider>
<EventProvider>
<AppProvider>
<AppStack/>
</AppProvider>
</EventProvider>
</FoodProvider>
</ImageProvider>
Hope that helps.
So, I'm trying to use React Navigation and I want to use the same Header component across all my screens.
I used the expo-cli to create the project.
In my MainTabNavigator.js file, I have a HomeStack and SettingsStack, and they look like this:
const config = Platform.select({
web: { headerMode: "screen" },
default: {}
});
const HomeStack = createStackNavigator(
{
Home: HomeScreen
},
config
);
HomeStack.navigationOptions = {
tabBarIcon: ({ focused }) => (
<NavButton
focused={focused}
name={focused ? "star" : "create"}
label={focused ? "Save" : "New"}
/>
)
};
HomeStack.path = "";
At the bottom of the file, I have my tabNavigator
const tabNavigator = createBottomTabNavigator(
{
HomeStack,
SettingsStack
},
{
tabBarOptions: {
inactiveTintColor: "#fff",
labelStyle: {
fontSize: 14
},
showLabel: false,
style: {
backgroundColor: "#fff",
borderTopColor: "#fff",
height: 70,
paddingBottom: 10,
paddingTop: 10
}
}
}
);
tabNavigator.path = "";
export default tabNavigator;
I tried adding a <Header /> component in the navigatorOptions and defaultNavigationOption above the tabBarOptions in the createBottomTabNavigator function.
Similar to this:
...
{
navigationOptions: {
header: <Header />
},
tabBarOptions: {
inactiveTintColor: "#fff",
labelStyle: {
fontSize: 14
},
...
but this just gives me a blank header... not using my component.
I currently have the functionality I want, but I have to go into each Screen file and add:
HomeScreen.navigationOptions = {
header: <Header />
};
Currently using "react-navigation": "^3.11.0"
Any help is appreciated!
This is how I'm achieving this.
Create a CustomHeader component like this:
const MyHeader = (navigation) => {
return {
header: props => <MyCustomHeader {...props} />,
headerStyle: { backgroundColor: '#fff' },
headerTintColor: '#000',
};
}
Then include it in defaultNavigationOptions in your stack navigator
const AppNavigator = createStackNavigator(
{
Launch: {
screen: LaunchComponent,
}
},
{
initialRouteName: "Launch",
defaultNavigationOptions: ({ navigation }) => {
return MyHeader(navigation)
}
}
)
I wanted to achieve similar solution for DrawerNavigator. The only way to do this was to create HOC component.
import React, {Fragment} from 'react';
const NavHeader = props => {
// ... NavHeader code goes here
};
export const withHeader = Component => {
return props => {
return (
<Fragment>
<NavHeader {...props} />
<Component {...props} />
</Fragment>
);
};
};
Then in your Drawer you do:
<Drawer.Navigator>
<Drawer.Screen
name={ROUTES.DASHBOARD}
component={withHeader(DashboardContainer)} // <--- Wrap it around component here.
/>
</Drawer.Navigator>
Set your StackNavigator as parent of your BottomTabNavigator, doing it will let you have a single header for all the bottomTabs.
If you use this approach, and need for some reason to have a backButton on your header you have to change it manually yourself.
const bottomTabNavigator = createBottomTabNavigator(bottomRoutesConfig, bottomNavigatorConfigs)
createStackNavigator:({ tabNavigator : bottomTabNavigator },{ defaultNavigationOptions :{ header : <Header/> }})
Doing this you can use one global header for all the screens, but as said, to change something on the header based off the screen you have to change it yourself, making the header know in what location the user currently is.
set header to null in every screen and load your component on each screens
class HomeScreen extends Component {
static navigationOptions = {
header: null,
};
render(){
return(
<View style={{flex:1}}>
<Header />
</View>
)
}
}
so, the default header of react-navigation will be null and you can load your custom component as header
OR
class HomeScreen extends React.Component {
static navigationOptions = {
// headerTitle instead of title
headerTitle: <Header />,
};
/* render function, etc */
}
use headerTitle instead of title to load your custom component
I am writing an app using Expo (React Native framework). Whenever the state of a component is changed with setState(...) method, the component should re-render to show the latest state data. This works with basic React Native components such as View, Text, Button (I have tried these three), but it does not re-render here, where I use custom component TabView. This is stated in the documentation about component:
"All the scenes rendered with SceneMap are optimized using React.PureComponent and don't re-render when parent's props or states change. If you need more control over how your scenes update (e.g. - triggering a re-render even if the navigationState didn't change), use renderScene directly instead of using SceneMap."
I don't quite understand how to manage this. I would like the component to re-render whenever the state is changed, in this case, after clicking the button and calling the function writeData().
Documentation of the TabView component is here: https://github.com/react-native-community/react-native-tab-view .
import React from 'react';
import { TabView, SceneMap } from 'react-native-tab-view';
import { StyleSheet, Text, View, Dimensions, Button, ToastAndroid } from 'react-native';
export default class MojTabView extends React.Component {
state = {
index: 0,
routes: [
{ key: 'allEvents', title: 'Všetko' },
{ key: 'sortedEvents', title: 'Podľa druhu' },
{ key: 'myEvents', title: 'Moje akcie'}
],
name: "Robert"
};
MyEvents = () => (
<View style={[styles.scene, { backgroundColor: 'white' }]}>
<Text>Some content</Text>
<Button
style={{ margin: 5 }}
onPress={this.writeData}
title="Write data"
color="#841584"
/>
<Text>Name in state: {this.state.name}</Text>
</View>
);
SortedEvents = () => (
<Text>Some content</Text>
);
AllEvents = () => (
<Text>Some other content</Text>
);
writeData = () => {
ToastAndroid.show("button click works!", ToastAndroid.LONG);
this.setState({ name: "Richard"});
}
render() {
return (
<TabView
navigationState={this.state}
renderScene={SceneMap({
allEvents: this.AllEvents,
myEvents: this.MyEvents,
sortedEvents: this.SortedEvents
})}
onIndexChange={index => this.setState({ index })}
initialLayout={{ width: Dimensions.get('window').width }}
/>
);
}
}
I spent a few hours trying to achieve that, without solution. This is my first StackOverflow question, thanks.
Okay, I was finally able to come up with a nice solution. The point is to define content of individual tabs/routes in a separate file as a typical React Native component with its own state, not inside this MyTabView component, as it is made even in the example in the documentation about TabView. Then it works as it should.
Simple example:
This is content of one of the tabs:
import React from 'react';
import { Text, View, Button } from 'react-native';
export default class MyExampleTab extends React.Component {
state = {
property: "Default value",
}
writeData = () => {
this.setState({
property: "Updated value"
})
}
render() {
return (
<View>
<Button
style={{ margin: 5 }}
onPress={this.writeData}
title="Change the state and re-render!"
color="#841584"
/>
<Text>Value of state property: {this.state.property}</Text>
</View>
)
}
}
This is how I reference it in the MyTabView component:
import MyExampleTab from './MyExampleTab'
...
export default class MyTabView extends React.Component {
...
render() {
return (
<TabView
navigationState={this.state}
renderScene={SceneMap({
...
someKey: MyExampleTab,
...
})}
onIndexChange={index => this.setState({ index })}
initialLayout={{ width: Dimensions.get('window').width }}
/>
)
So I don't use <MyExampleTab />, as I would normally using custom component, but write it as it is, MyExampleTab. Which quite makes sense. And on the button click the state changes and tab re-renders.
I changed to self implemented renderScene and inner component now get re-rendered
<TabView
navigationState={{ index, routes }}
renderScene={({ route }) => {
switch (route.key) {
case 'first':
return <FirstRoute data={secondPartyObj} />;
case 'second':
return SecondRoute;
case 'third':
return ThirdRoute;
case 'forth':
return ForthRoute;
default:
return null;
}
}}
onIndexChange={setIndex}
initialLayout={{ width: layout.width }}
renderTabBar={renderTabBar}
/>
The two pieces of state that should triger re-render of your TabView are:
index.
routes
They've done that for performance optimization purposes
if you want to force re-rendering your TabView if you're changing any other state value that has nothing to do with these values ... then according to the docs ... you need to provide your own implementation for renderScene like:
renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'music':
return ;
case 'albums':
return ;
}
};
in this case:
You need to make sure that your individual routes implement a
shouldComponentUpdate to improve the performance.
Building on what #druskacik wrote above, you can actually pass a component with props too
import MyExampleTab from './MyExampleTab'
...
export default class MyTabView extends React.Component {
...
render() {
return (
<TabView
navigationState={this.state}
renderScene={SceneMap({
...
someKey:() => <MyExample foo={foodata} />,
...
})}
onIndexChange={index => this.setState({ index })}
initialLayout={{ width: Dimensions.get('window').width }}
/>
)
I've been working with the default tabs project created with create-react-native-app.
So I created my own screen where this.props.navigation is accessible in the main (export default class) component. It works fine to do navigate('Search') from the button titled 'nav default'.
However, after many tries, I couldn't navigate from either the button in the headerRight or in my custom component MyComponent.
How do I change my alerts to instead do navigate('Search') like the main component?
import React from 'react';
import { Button, Dimensions, ScrollView, StyleSheet, Text, View } from 'react-native';
class MyComponent extends React.Component {
// i wish i could navigate from here
render() {
//const { navigate } = this.props.navigation; // this causes TypeError undefined is not an object (evaluating this.props.navigation.navigate)
return (
<View>
<Button title="nav from component" onPress={() => alert('This should navigate, not alert')} color="red" />
</View>
);
}
}
export default class MyScreen extends React.Component {
// i wish i could navigate from here too
static navigationOptions = {
title: 'Test',
headerRight: (
//<Button onPress={() =>navigate('Search')} /> // how do I achieve this?
<Button title="nav from header" onPress={() => alert('This should navigate, not alert')} />
),
};
render() {
const { navigate } = this.props.navigation;
return (
<ScrollView style={styles.container}>
<Button onPress={() =>navigate('Search')} title="nav default" />
<MyComponent />
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 15,
backgroundColor: '#fff',
},
});
There are two different problems. First, navigation is only passed as a prop to the components that are navigation screens. So, to access it from other components, such as your MyComponent, you have to pass it through props <MyComponent navigation={navigation} /> and then you can use this.props.navigation.navigate inside it. You can also use the withNavigation higher order component, but in that case the first approach is better.
The other problem is, if you want to access the navigation prop in navigationOptions, you should define it as a function, and not as an object. The implementation would be something like this:
static navigationOptions = ({ navigation }) => ({
title: 'Test',
headerRight: (
<Button onPress={() =>navigation.navigate('Search')} />
),
});
I need pass some props to my child component, but I have a tabNavigator between the file that contains the props and the file that will receive the props.
Example:
Home.js: (this is the component that holds the props)
export default class Home extends Component<{}>
{
render() {
return (
<View style={{ flex: 1 }}>
<TabNavigator
changeScreen={() => this.props.navigation.navigate('Profile'))//this is the props
/>
</View >
);
}
}
TabNavigator.js:
const MainScreenNavigator = TabNavigator({
tab1: {
screen: ChatScreen
},
tab2: {
screen: NoMatterScreen
}
})
ChatScreen.js: (this is the component that I need to use the props sended form Home.js
export default class ChatScreen extends Component<{}>
{
render() {
return (
<View style={{ flex: 1 }}>
<Button
onPress={() => this.props.changeScreen())
/>
</View >
);
}
}
The problem here is the TabNavigator, how can ChatScreen receive a props from Home if there's a TabNavigator between both? Just to make clear, the props that Home are sending is from a DrawerNavigator, inside my DrawerNavigator I have Home and Profile screen.
I would recommend you to use Redux but here a link you can use to solve your issue with some changes in your code.
https://reactnavigation.org/docs/intro/nesting
Or you can still give it a try by adding TabNavigator to home.js
export default class Home extends Component<{}>
{
render() {
return (
<View style={{ flex: 1 }}>
<MainScreenNavigator
changeScreen={() => this.props.navigation.navigate('Profile'))//this is the props
/>
</View >
);
}
}
const MainScreenNavigator = TabNavigator({
tab1: {
screen: ChatScreen
},
tab2: {
screen: NoMatterScreen
}
})
When you pass props to a TabNavigator, it is distributed to all its children.