Have been struggling a couple of days now trying to figure out how to toggle a search bar in the react navigation.
My approach has been to
static navigationOptions = ({navigation}) => {
return {
title: 'Header Title',
headerLeft: (
{navigation.params.state.search ? <searchfield query={text => navigation.setParams(text)} > : <settings>}
),
headerRight: (
<TouchableOpacity style={{ marginHorizontal: 10 }}>
<Icon name="search" size={28} color="#5751D9" />
</TouchableOpacity>
)
}}
I then wanted to add some logic to the headerLeft so it either returns the cog icon button component or an TextInput component (plan to pass the text to setParams and use it as a filter in the list component below the header) but I can't seem to figure out how to pass down a state or state handler as props when I'm not navigating to it.. It's the initial screen.
Hook a function to your setParams inside componentDidMount which will
be called on searchedText change, use this function to setState.
componentDidMount() {
this.props.navigation.setParams({onSearchText: (searchedText) => this.onSearchText(searchedText)});
}
onSearchText(searchedText) {
//update your list using this searchedText
this.setState({searchedText})
}
Now call the function onSearchText() when searchedText changes,
static navigationOptions = ({navigation}) => {
return {
title: 'Header Title',
headerLeft: (
{navigation.params.state.search ? <searchfield query={text => onSearchText(text)} > : <settings>}
),
headerRight: (
<TouchableOpacity style={{ marginHorizontal: 10 }}>
<Icon name="search" size={28} color="#5751D9" />
</TouchableOpacity>
)
}}
Hope it will help you ...
Related
I have a tab bar that looks like this:
The two side buttons are stack navigators (Learn and Journal) and the middle button needs to navigate the Journal Stack, and depending on what screen in the Journal Stack the user is on, it needs to say and do different things.
const Tab = createBottomTabNavigator();
const TabBarIcon = ({ icon, title, focused }) => {
return (
<View style={styles.iconContainer}>
<FontAwesomeIcon
icon={icon}
color={focused ? Colors.neutral[4] : Colors.neutral[6]}
size={24}
style={styles.icon}
/>
<Text style={[styles.iconText, focused && styles.iconTextFocused]}>
{title}
</Text>
</View>
);
};
const NullScreen = () => null;
const TabNavigator = () => {
return (
<Tab.Navigator
initialRouteName="Journal"
screenOptions={({ route }) => ({
...defaultNavOptions,
headerShown: false,
tabBarStyle: { backgroundColor: Colors.neutral[3] },
tabBarShowLabel: false,
})}
>
<Tab.Screen
name="Learn"
component={LearnStackNavigator}
options={{
tabBarIcon: ({ focused }) => (
<TabBarIcon
focused={focused}
title={'Learn'}
icon={faUserGraduate}
/>
),
}}
/>
<Tab.Screen
name="Null Screen"
component={NullScreen}
options={{
tabBarButton: ({ focused }) => (
<View
style={{
position: 'relative',
bottom: 25,
width: 80,
height: 80,
borderRadius: '50%',
backgroundColor: 'grey',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
shadowColor: 'black',
shadowOpacity: 0.3,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 3,
}}
>
<TouchableOpacity onPress={() => Alert.alert('hello world')}> // This is the button that I want use for useful things
<View style={[styles.iconContainer, styles.paddingBottom10]}>
<FontAwesomeIcon
icon={faPlus}
color={focused ? Colors.neutral[4] : Colors.neutral[6]}
size={32}
/>
<Text style={styles.iconText}>{'Add Sport'}</Text>
</View>
</TouchableOpacity>
</View>
),
}}
/>
<Tab.Screen
name="Journal"
component={LogbookStackNavigator}
options={{
tabBarIcon: ({ focused }) => (
<TabBarIcon focused={focused} title={'Journal'} icon={faPenAlt} />
),
}}
/>
</Tab.Navigator>
);
};
And here is what the LogbookStackNavigator looks like:
const LogbookStack = createStackNavigator();
const LogbookStackNavigator = () => {
return (
<LogbookStack.Navigator
screenOptions={{
...defaultNavOptions,
headerBackTitleVisible: false,
}}
>
<LogbookStack.Screen
name="Screen1"
component={screen1Component}
options={defaultNavOptions}
/>
<LogbookStack.Screen
name="Screen2"
component={screen2Component}
options={defaultNavOptions}
/>
<LogbookStack.Screen
name="Screen3"
component={screen3Component}
options={entryScreenOptions}
/>
<LogbookStack.Screen
name="Screen4"
component={screen4Component}
options={SaveLogbookScreenOptions}
/>
<LogbookStack.Screen
name="Screen5"
component={screen1Component5}
options={defaultNavOptions}
/>
</LogbookStack.Navigator>
);
};
I know how to use navigation.setOptions, but it only affects the immediate parent navigator, not the grandparent navigator.
Another thing I tried was to make the big circle button on the page itself, but it always rendered underneath the Tab Navigator. If there was a way to make it render above, I think I could just use that. I tried 'position: 'absolute', etc and it always rendered underneath the tab navigator. As it is, I had to basically make a dummy screen in the tab navigator to give me the button on top.
What I need to be able to do, is use big circle button on the Tab Navigator, to navigate to different screens in the LogbookStackNavigator. How do I do that?
Also, I need the title to change from "Add Sport" to "Add " depending on what screen the LogbookStackNavigator is on. How do I do that?
Thanks for your help
Finally figured this out. You have to use react-native-portalize. Just wrap the elements you want to be rendered on top in a
<Portal></Portal>. This will place it above a Bottom Tab navigator.
import { Portal } from 'react-native-portalize';
const FooterButton = () => {
return(
<Portal>
<View>
<Text>I appear above the Tab Navigator!</Text>
</View>
</Portal>
);
export default FooterButton;
Don't forget to wrap the whole app in the the Host:
//In app.js
import { Host } from 'react-native-portalize';
const App = () => {
return (
<Host>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
</Host>
)
}
export default App;
NOTE: The elements inside the Portal, do not clear when the navigator navigates to another screen. So to get around this, you have to only display the Portal, when the screen is active. Thankfully React Navigation 5+ provides a useIsFocused hook that accomplishes this perfectly.
import { Portal } from 'react-native-portalize';
import { useIsFocused } from '#react-navigation/native';
const FooterButton = () => {
const isFocused = useIsFocused();
// Only display the button when screen is focused. Otherwise, it doesn't go away when you switch screens
return isFocused ? (
<Portal>
<View style={styles.buttonContainer}>
<View style={styles.footer}>{props.children}</View>
</View>
</Portal>
) : null;
};
export default FooterButton;
If you want a modal-style popup, you can wrap react-native-modalize and wrap it with react-native-modalize
Thanks to livin52 on Reddit for the solution
I'm quite new to React Native, I want to open another screen when a button is tapped, the screen that I want to show is already created. I have used TouchableOpacity as my button and used navigation on "onPress" prop. My app is already using Stack navigator and Tab navigator so I have installed those packages.
I have tried but I'm getting an error "undefined is not an object (evaluating 'navigation.navigate')"
Please help.
In the screen where I'm showing the button:
const myWebview = ({ navigation }) => {
return (
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate("NewListingScreen")}
>
<Text style={{ color: "#ffffff", fontWeight: "bold" }}>
My Button Title
</Text>
</TouchableOpacity>
</View>
);
};
On my Tab Navigator (The screen works fine):
<Tab.Screen
name={routes.newListingScreen}
component={NewListingScreen}
options={({ navigation }) => ({
tabBarButton: () => (
<NewListingButton
onPress={() => {
navigation.navigate(routes.newListingScreen);
dispatch({
type: "SET_NEW_LISTING_SCREEN",
newListingScreen: true,
});
}}
/>
),
tabBarVisible: !user,
})}
/>
When I use const navigation = useNavigation();
I get this error:
My first guess is that the navigation object itself is undefined here. Navigation prop is only available on screens. -
const myWebview = ({ navigation }) => {
Using hooks is a better alternative to passing navigation objects to child components.
import { useNavigation } from '#react-navigation/native';
function NotificationsScreen() {
const navigation = useNavigation();
return(
...
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate('NewListingScreen')}
>
...
);
}
I am working on a hobby gym management app, and I am puzzled by the mechanism of sharing state between three components in React-Native.
My three components are:
1. Schedule:
[...]
function Schedule() {
return (
<Stack.Navigator
initialRouteName="Monday"
screenOptions={{
headerStyle: { backgroundColor: "#f58220" },
headerTintColor: "#fff",
headerTitleStyle: { fontWeight: "bold" },
headerRight: () => <SwitchButton />,
}}
>
<Stack.Screen
name="TabStack"
component={TabStack}
options={{ title: "Aerobic Schedule" }}
/>
</Stack.Navigator>
);
}
export default Schedule;
I want the SwitchButton button in my Schedule component (1.) to alternate between DATA_AEROBIC and DATA_KIDS arrays props of the FlatList in (2.) based on the content of the listAerobic boolean variable.
2. MondayPage:
[...]
const MondayPage = () => {
const [selectedId, setSelectedId] = useState(null);
const [listAerobic, setListAerobic] = useState(true);
const renderItem = ({ item }) => {
const backgroundColor = item.id === selectedId ? "#6e3b6e" : "#f9c2ff";
return (
<Item
item={item}
onPress={() => setSelectedId(item.id)}
style={{ backgroundColor }}
/>
);
};
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1, padding: 5 }}>
<SafeAreaView style={styles.container}>
<FlatList
data={listAerobic ? DATA_AEROBIC : DATA_KIDS}
renderItem={renderItem}
keyExtractor={(item) => item.id}
extraData={selectedId}
/>
</SafeAreaView>
</View>
</SafeAreaView>
);
};
However, I don't know how to link the listAerobic boolean variable to the state of the SwitchButton component (3.) , and how to make it toggle on and off.
3. SwitchButton:
const SwitchButton = () => {
const [isEnabled, setIsEnabled] = useState(false);
const toggleSwitch = () => setIsEnabled(previousState => !previousState);
return (
<View style={styles.container}>
<Switch
trackColor={{ false: "#767577", true: "#81b0ff" }}
thumbColor={isEnabled ? "#f5dd4b" : "#f4f3f4"}
ios_backgroundColor="#3e3e3e"
onValueChange={toggleSwitch}
value={isEnabled}
/>
<Text> aerobic/kids</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
marginRight: 5,
padding: 5,
}
});
export default SwitchButton;
Any guidance would be awesome! I mention I have really tried to look it up on different tutorials, but I can't seem to get the gist of it. It is my first project in React/React-Native.
Many thanks!
I think you just need 'value' to accept a prop passed into it on the switch button. Then wherever you use switch button just pass a boolean value into it from state e.g.
<SwitchButton enabled={this.state.switchEnabled}/>
As for setting state 'globally' so this.state.switchEnabled can be updated from various places / accessible all over the app you need to look into state management tools like Redux (or I hear 'React Hooks' is now a thing and preferred....)
Is it possible to use ternary operator within a built-in component tag? For instance, I am using Touchable Opacity from React Native (Native Base):
type ItemProps = {
title: string;
face: string;
};
export const Item: React.FunctionComponent<ItemProps> = ({
title,
face,
}) => {
const [showAddFriendPage, setShowAddFriendPage] = useState(false);
const toggleAddFriendPage = () => {
setShowAddFriendPage(showAddFriendPage ? false : true);
};
return (
<TouchableOpacity activeOpacity={0.8}
onPress={() =>
setShowAddFriendPage(true)
} >
<View>
<Thumbnail small source={{ uri: face }} style={styles.thumbnail} />
<Text numberOfLines={1} style={styles.title}>
{title}
</Text>
<AddFriendPage
showAddFriendPage={showAddFriendPage}
toggleShowPage={toggleAddFriendPage}
/>
</View>
</TouchableOpacity>
);
};
Currently the onPress navigation is applied to all Items regardless of what title or face was used. I want to introduce a conditional navigation. For instance, if the
title == 'news'
then onPress.... Since we can't use if else statements within jsx, I was trying ternary operators:
<TouchableOpacity activeOpacity={0.8}
{title == 'news'? {
onPress={() =>
setShowAddFriendPage(true)
}
} }
/>
But this clearly doesn't work. I get '...' expected.on title.
No value exists in scope for the shorthand property 'onPress'. Either declare one or provide an initializer.ts(18004)on onPressand
Cannot find name 'setShowAddFriendPage'.
you can do like this
<TouchableOpacity activeOpacity={0.8}
onPress={() =>{
if(title == 'news'){
setShowAddFriendPage(true)
}
}}
/>
You can use spread operator (...) to conditionally add props to components.
<TouchableOpacity
activeOpacity={0.8}
{...(title == 'news' && { onPress: () => setShowAddFriendPage(true) })}
/>
This way component will have onPress prop whenever title equals to 'news'
Use useCallback to create an onPress function that has different behavior based on your condition.
const onPress = useCallback(() => {
if (title === 'news') {
setShowAddFriendPage(true)
}
}, [title])
It has a dependency on title, so it will be re-created, and the component re-rendered only if title changes.
Then use it as such:
<TouchableOpacity activeOpacity={0.8} onPress={onPress}>
{/* … */}
</TouchableOpacity>
I am using React Native and React Navigation to build a simple app.
I have got the basic structure working with stub state but I am having problem with changing state via callback and re-render.
In my screen, I have simple start button
`<View style={styles.buttonContainer}>
<TouchableOpacity
style={[myStyles.buttonStyle, { backgroundColor: color }]}
onPress={() => handlePress(button.title)}
>
<Text style={myStyles.textStyle}>{button.title}</Text>
</TouchableOpacity>
</View>`
Problem:
After I update my parent Component state, my child component does not instantly render to match the state change. I understood React will re-render all child components when parent state is changed?
Instead, I have to move back to previous screen and navigate again to my button screen to see that the button's color and text has changed correctly.
I've read about requiring a componentWillReceiveProps(nextProps) handler but I am not sure how to use it. I put a console.log('nextProps', nextProps) inside but it does not get fired.
From navigation perspective, the Root component is on index[0] and my button view is at index[3] so it's the 3rd screen from the root.
EDIT 1: Added Code
myButton screen:
export class TeamsScreen extends React.Component {
static navigationOptions = ({ navigation }) => ({
title: `${navigation.state.params.game.name}: Select Team`,
headerTintColor: 'white',
headerStyle: {
backgroundColor: 'black',
},
headerVisible: true
})
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps);
}
render() {
const { navigate, setParams } = this.props.navigation;
const { game, player, setGameState } = this.props.navigation.state.params;
const color = game.status === 'Start' ? 'green' : 'red';
const index = game.indexOf(player);
const status = game.status;
console.log('index', index);
console.log('status', status);
return (
<View style={styles.container}>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[myStyles.buttonStyle, { backgroundColor: color }]}
onPress={() => setGameState(index, status)}
>
<Text style={myStyles.textStyle}>{game.status}</Text>
</TouchableOpacity>
</View>
<View style={styles.buttonContainer}>
<Button
onPress={() => navigate('ChangeDriverScreen', { team, game })}
title='Change Driver'
/>
</View>
<View style={{ marginTop: 40, marginBottom: 20 }}>
<Text style={{ fontSize: 16, color: 'white', alignSelf: 'center' }}>Teams</Text>
</View>
<View style={{ height: 250 }}>
<FlatList
data={player.teams}
renderItem={({item}) =>
<View style={styles.buttonContainer}>
<Button
onPress={() => navigate('TeamSelectedStartScreen', { team: item })}
title={item.name}
/>
</View>}
keyExtractor={item => item.name}
/>
</View>
<Image
style={{ alignSelf: 'center', justifyContent: 'flex-end', height: 75, width: 250, resizeMode: 'stretch'}}
source={require('./images/icons/playerPlaceholder.png')}
/>
</View>
)}}
Then the onPress function that is called back:
setGameState = (gameIndex, status) => {
console.log('setGameState', gameIndex, status);
console.log('gameStateBefore', this.state.game);
const newGameState = this.state.game.map(t => {
console.log(this.state.game.indexOf(t));
if (this.state.game.indexOf(t) === gameIndex) {
const newStatus = status === 'Start' ? 'Stop' : 'Start';
t.status = newStatus; /*eslint no-param-reassign: "off"*/
console.log('inside if', t.status);
console.log('inside if game', t);
return t;
}
return t;
});
console.log('new Game State', newGameState);
this.setState(() => ({
game: newGameState
}));
}
So the setState method works (as re-navigating back to screen 3 shows the correct state but core question is how to get immediate re-render of screen 3 when setState is called from Screen 0.