Previously I have set the headerRight option in the root component that renders child component with Screen options like this
export default function App() {
return (
<RecipeProvider>
<NavigationContainer>
<Stack.Navigator initialRouteName="Recipes">
<Stack.Screen
name="Recipes"
component={RecipeList}
options={({ navigation }) => ({
headerRight: () => (
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate("New Recipe")}
>
<FontAwesomeIcon icon={faPlus} size={20} />
</TouchableOpacity>
),
})}
/>
and now I would like to move the headerRight inside the component definition (so I don't end up with a huge App file with details that are only relevant to the screen component themselves)
I have read other solutions and tried the following
export default function RecipeList({ navigation }) {
const { recipes } = useContext(RecipeContext);
return (
<View style={styles.container}>
<FlatList
numColumns={2}
data={recipes}
keyExtractor={(recipe: Recipe) => recipe.id}
renderItem={({ item }) => {
return (
<RecipeItem
name={item.name}
minutes={item.minutes}
image={item.image}
title="Go to Detail Screen"
onPress={() => {
navigation.navigate("Recipe Details", { item });
}}
/>
);
}}
/>
</View>
);
}
RecipeList.navigationOptions = ({ navigation }) => ({
headerRight: () => (
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate("New Recipe")}
>
<FontAwesomeIcon icon={faPlus} size={20} />
</TouchableOpacity>
),
});
But the headerRight button doesn't show no more ... any ideas ?
Thank you all <3
My headerRight is below and it works for me
EditScreen.navigationOptions = navData => {
const submitFn = navData.navigation.getParam('submit');
return {
headerTitle: 'Edit Data'
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title="Save"
iconName={
Platform.OS === 'android' ? 'md-checkmark' : 'ios-checkmark'
}
onPress={submitFn}
/>
</HeaderButtons>
)
};
};
Related
I'm new to React Native and I'm having some difficulties with react navigation.
I'm building a weather app that fetches data from an API. There are 6 cities that I need to render to the main screen and when you press on a city it should take you to a different screen with the weather for the day for that particular city.
I can't seem to wrap my head around the last part - how to navigate to a different screen with the data when pressing on that particular city
I will appreciate any help. Thanks!
App:
const App = () => {
const [weatherData, setWeatherData] = useState([]);
const netInfo = useNetInfo();
const dataFetchAndProcess = async url => {
let data = await fetchDataFunc(url);
storeASData(data);
let takeData = await getASData();
let processData = await processDataFunc(takeData);
return setWeatherData(processData);
};
useEffect(() => {
dataFetchAndProcess(fetchUrl);
}, []);
return (
<View style={styles.container}>
<Header />
{weatherData && (
<CitiesContainer weatherData={weatherData}></CitiesContainer>
)}
</View>
);
};
CitiesContainer - generates cities from flatlist:
const CitiesContainer = ({weatherData}) => {
return (
<FlatList
style={{flex: 1, width: '100%'}}
data={weatherData}
renderItem={({item}) => (
<TouchableOpacity>
<CityContainer
date={item.date}
forecast={item.forecast}
cityName={item.cityName}
cityImg={item.cityImg}
/>
</TouchableOpacity>
)}
keyExtractor={(item, index) => index.toString()}
/>
);
};
CityContainer:
const CityContainer = ({cityName, cityImg, date, forecast}) => {
return (
<NavigationContainer>
<CityContext.Provider value={cityName}>
<Stack.Navigator initialRouteName="City">
<Stack.Screen name={cityName}>
{props => <City {...props} cityImg={cityImg} cityName={cityName} />}
</Stack.Screen>
<Stack.Screen name={`${cityName} forecast`}>
{props => (
<ForecastContainer
{...props}
date={date}
cityName={cityName}
forecast={forecast}
/>
)}
</Stack.Screen>
</Stack.Navigator>
</CityContext.Provider>
</NavigationContainer>
);
};
City:
const City = ({cityName, cityImg, date, forecast}) => {
// const cityName = useContext(CityContext);
return (
<TouchableOpacity>
<Text>{cityName}</Text>
<CityImage cityImg={cityImg} />
</TouchableOpacity>
);
};
ForecastContainer:
const ForecastContainer = ({cityName, date, forecast}) => {
return (
<View>
<Text>{cityName}</Text>
<Text>{date}</Text>
<FlatList
style={{flex: 1, width: '100%'}}
data={forecast}
renderItem={({item}) => (
<Forecast temp={item.temp} time={item.time}></Forecast>
)}
keyExtractor={(item, index) => index.toString()}
/>
{/* <Forecast forecast={forecast}></Forecast> */}
</View>
);
};
Forecast:
const Forecast = ({temp, time}) => {
return (
<View>
<Text>{temp}</Text>
<Text>{time}</Text>
</View>
);
};
After hours of research and head-banging I figured it out. I needed to pass params to the route to make it show data specific to each city:
App
<NavigationContainer>
<View style={styles.container}>
<Header />
<Stack.Navigator>
<Stack.Screen name="Home">
{props => <CitiesContainer {...props} weatherData={weatherData} />}
</Stack.Screen>
<Stack.Screen name="Forecast">
{props => (
<ForecastContainer {...props} weatherData={weatherData} />
)}
</Stack.Screen>
</Stack.Navigator>
</View>
</NavigationContainer>
CityContainer - onPress calls the Forecast page with an optional second param. The second param is crucial in this case as it's used to determine what data to render on the Forecast page
const CityContainer = ({cityName, cityImg, navigation}) => {
return (
<TouchableOpacity
onPress={() => navigation.navigate('Forecast', {cityName})}>
<City
navigation={navigation}
cityName={cityName}
cityImg={cityImg}></City>
</TouchableOpacity>
);
};
ForecastContainer - takes in the weatherData array, as well as the route arguments. The route argument is necessary to obtain the cityName param
const ForecastContainer = ({weatherData, route}) => {
const {cityName} = route.params;
const cityFinder = data => {
return data.filter(obj => obj.cityName === cityName);
};
return (
<FlatList
ListHeaderComponent={
<>
<Text>{cityName}</Text>
<Text>{cityFinder(weatherData)[0].date}</Text>
</>
}
data={cityFinder(weatherData)[0].forecast}
renderItem={({item}) => <Forecast temp={item.temp} time={item.time} />}
keyExtractor={(item, index) => index.toString()}
/>
);
};
First thing that you have to pay some attention to, is that you are creating multiple navigation containers. So, you'll not be able to navigate between screens like that. Try to move your navigation container for a high-level component, some component that wraps all your screens.
Besides, you'll need to specify the action of moving between the screens to your component on the press event.
Some documentation that can help you with more details:
Setup React Navigation: https://reactnavigation.org/docs/hello-react-navigation
Moving between screens: https://reactnavigation.org/docs/navigating/
Modal only displays button but not FlatList item.
I am trying to build a custom app picker for my react-native-app which renders a list of items on a modal.
AppPicker.js
const AppPicker = ({ icon, items, placeholder }) => {
const [modalVisible, setModalVisible] = React.useState(false)
return (
<>
<TouchableWithoutFeedback onPress={() => setModalVisible(true)} >
<View style={styles.container}>
{icon && <MaterialCommunityIcons name={icon} size={20} style={styles.icon} />}
<AppText style={styles.text}> {placeholder} </AppText>
<MaterialCommunityIcons name="chevron-down" size={20} />
</View>
</TouchableWithoutFeedback>
<Modal visible={modalVisible} animationType="slide" >
<Button title="Close" onPress={() => setModalVisible(false)} />
<FlatList
data={items}
keyExtractor={item => item.value.toString()}
renderItem={({ item }) =>
<PickerItem
lable={item.label}
onPress={() => console.log(item)}
/>}
/>
</Modal>
</>
)
}
the <PickerItem/> that should be rendered by the flatlist is a component that just displays the items from the list.
PickerItem.js
const PickerItem = ({ label, onPress }) => {
return (
<TouchableOpacity onPress={onPress}>
<AppText> {label} </AppText>
</TouchableOpacity>
)
}
export default PickerItem;
when I randomly tap on the modal screen however the onPress() on the FlatList triggers the console.log(item) and the items are displayed on the console. Any help on this will be appreciated.
** App.js **
const categories = [
{ label: "furniture", value: 1 },
{ label: "clothing", value: 2 },
{ label: "phone", value: 3 },
]
export default function App() {
return (
<Screen style={styles.container}>
<AppPicker items={categories} icon="apps" placeholder="category" />
<AppTextInput icon="email" placeholder="email" />
</Screen>
);
}
NOTE: I am using expo to run the application.
I want to trigger an onPress function from the search icon in the navbar.
This is the search component:
function SearchIcon(props) {
const theme = useSelector(state => state.themer.theme);
return (
<Icon.Button
name="search"
size={22}
color={theme.icons}
backgroundColor={theme.top_tab}
onPress={() => {}}
/>
);
}
export default SearchIcon;
The search component is being called in the specific stack, where it's needed.
<Stack.Screen
name="Home"
component={Home}
options={({navigation, route}) => ({
...,
headerRight: props => (
<View style={{flexDirection: 'row'}}>
<SearchIcon />
<CartIcon navigation={navigation} />
</View>
),
})}
/>
On the home screen, I have an isSeacrhing constant that should change value from false to true and vice versa.
const [data, setData] = React.useState({
isSearching: false,
search: '',
...
});
// TRIGGERED BY SEARCH ICON IN NAV BAR
const toggleSearch = () => setData({...data, isSearching: !isSearching});
{data.isSearching == false ? (
<ScrollView
...
</ScrollView>
) : (
<View>
<TextInput
style={[styles.textInput, [{color: theme.text, ...FONTS.body4}]]}
value={data.search}
placeholder="Search..."
placeholderTextColor={theme.text}
onChangeText={()=>{}}
/>
</View>
)}
Is it possible to trigger the onPress function or is there another way I can make it work? The search icon is on two screen, does calling the same function make the TextInput appear on both?
Wherever you use <SearchIcon /> just add a prop in that like this
<SearchIcon onPress={() => { // Do Something }} />
Then in your SearchIcon
function SearchIcon(props) {
const theme = useSelector(state => state.themer.theme);
return (
<Icon.Button
name="search"
size={22}
color={theme.icons}
backgroundColor={theme.top_tab}
onPress={props.onPress} // Access it here like this
/>
);
}
export default SearchIcon;
I'm nesting screens and passing props to the screens such as header title, and a render of json.
Earlier today everything was working but now it gives me the error of getParams.
HomeStack.js, here in the title I get the title by the FlatList render in the screen which navigates to this one.
<Screen
name='errorHP'
component={errorHP}
options={{
headerTitle: () => <Header navigation={navigation} title={navigation.getParam('name')} />,
headerTitleAlign: 'center',}}
/>
HP.js, here the flatlist renders and will export the render to the page errorHP
<FlatList data={filteredSearch} keyExtractor={(item) => item.key} renderItem={({item}) => (
<TouchableOpacity onPress={() => navigation.navigate('errorHP', item)}>
<Card>
<Text style={globalStyles.titleText}> {item.name} </Text>
</Card>
</TouchableOpacity>
)} />
errorHP.js, here are listed the errors and after click, will pass again params to a new page which gets the error details.
export default function errorHP ({navigation}) {
const data = navigation.getParam('errors');
const errors = Object.keys(data);
return (
<View style={globalStyles.container}>
<FlatList data={errors} renderItem={({item}) => (
<TouchableOpacity>
<Card>
<Text style={globalStyles.titleText}> {item} </Text>
</Card>
</TouchableOpacity>
)} />
I've been messing around and still cant solve this problem.
Thanks for your attention!
You need to get params from the route prop:
export default function errorHP ({navigation, route}) {
const data = route.params.errors;
// whatever
}
And
<Screen
name="errorHP"
component={errorHP}
options={({ route, navigation }) => ({
headerTitle: () => (
<Header navigation={navigation} title={route.params.name} />
),
headerTitleAlign: 'center',
})}
/>
I am using React Navigation V5, I wanna custom drawer Navigation content which contents the image on top and some others navigation items unders
Here is my drawer items:
Image (custom view)
Profile
Products
Orders
Here is my code my custom drawer content.
export const CustomDrawerContent = props => {
return (
<SafeAreaView style={styles.customDrawer}>
<View
style={{ flex: 1 }}
>
<DrawerContentScrollView {...props}>
<TouchableNativeFeedback onPress={() => { console.log('go profile'); }}>
<View style={styles.userContainer}>
<View style={styles.imageContainer}>
<Image
style={styles.image}
source={{ uri: 'https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcTLCta_MQcJFd2kpz8HwXFm-6vxVqXzRUgCOIuhs94Q32GG8EeJ' }}
/>
</View>
<Text style={styles.name}>Nguyen van Admin</Text>
</View>
</TouchableNativeFeedback>
<DrawerItemList {...props} />
</DrawerContentScrollView>
<DrawerItem
label="Đăng xuất"
style={{
borderWidth: 1,
}}
labelStyle={{
color: 'black'
}}
icon={({ focused, color, size }) => <Ionicons
size={23}
color={color}
name={Platform.OS === 'android' ? 'md-exit' : 'ios-exit-outline'}
/>}
/>
</View>
</SafeAreaView>
);
}
So If the profile screen existed in drawer, By clicking to the image i can use
props.navigate("profile")
But if I remove the profile screen from the drawer screens. I can not navigate to profile anymore.
How do i archive navigate to profile screen without adding it the drawer screens?
Or Can I hide profile item from drawer items?
To hide a menu item from drawer, use Array.map(...) instead of <DrawerItemList {...props} /> in custom drawer content.,
{drawerItems.map((item, index) => {
return (
<DrawerItem
label={item.drawerLabel}
onPress={() => props.navigation.navigate(item.routeName)}
/>
);
})}
and add a useEffect hook to custom drawer content like below,
let [drawerItems, setDrawerItems] = useState([]);
useEffect(() => {
let drawerItemsList = [];
for (const key in props.descriptors) {
if (props.descriptors.hasOwnProperty(key)) {
if (!key.includes('profile')) {
const element = props.descriptors[key];
element.options.routeName = key.substring(0, key.indexOf('-'));
drawerItemsList.push(element.options);
}
}
}
setDrawerItems(drawerItemsList);
}, []);
Another approach.,
Create an Array like below in the custom drawer content.,
const drawerItemsList = [
{
drawerLabel: 'Products',
drawerIcon: 'product',
routeName: 'products',
active: true,
},
{
drawerLabel: 'Orders',
drawerIcon: 'order',
routeName: 'orders',
active: false,
},
];
let [drawerItems, setDrawerItems] = useState(drawerItemsList);
and instead of <DrawerItemList {...props} /> use <Flatlist /> like below.,
<View>
<FlatList
data={drawerItems}
keyExtractor={(item)=>item.routeName.trim()}
renderItem={({item,index})=>(
<DrawerItem
label={item.drawerLabel}
icon={({color, size}) => <Ionicons name={item.drawerIcon} color={item.active?'#1e90ff':'#ccc'} size={size} />}
labelStyle={[item.active?{color: '#1e90ff'}:{color: '#ccc'}]}
style={item.active?{backgroundColor: '#1e90ff20'}:null}
onPress={() => {
drawerItemsList.forEach((element,i) => {
i!==index?element.active=false:element.active=true
});
setDrawerItems(drawerItemsList)
props.navigation.navigate(item.routeName)
}}
/>
)}
/>
</View>