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/
Related
I have a nested navigators, NativeStack -> MatTopTabStack (as per documentation), so when I navigate to a screen, an empty space appears which from the size of it looks like top tabs section, but is invisible... below is the code and some screenshot...
Relevant Code:
const topTab = createMaterialTopTabNavigator();
const MatTopStack = () => {
return (
<topTab.Navigator>
<topTab.Screen name="General" component={General} />
<topTab.Screen name="Subscribed" component={Subscribed} />
</topTab.Navigator>
);
};
const Stack = createNativeStackNavigator();
const MainStack = () => {
return (
<Stack.Navigator
initialRouteName="MatTopStack"
screenOptions={{
headerTitle: 'MYAPP',
headerRight: () => <Text>Logout</Text>,
}}>
<Stack.Screen name="MatTopStack" component={MatTopStack} />
<Stack.Screen
options={{ header: () => null }}
name="StrategyDetails"
component={StrategyDetails}
/>
</Stack.Navigator>
);
};
Now, In strategy details screen, I want to show a custom header, but it is not working as I'm getting that empty space in between...
const StrategyDetails = () => {
useLayoutEffect(() => {
navigation.setOptions({
header: () => (
<SafeAreaView>
<View style={styles.headerContainer}>
<TouchableOpacity>
<Text><--</Text>
</TouchableOpacity>
<Text>{data.strategyName}</Text>
<TouchableOpacity>
<Text>BUY</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
),
});
}, [data]);
return (
...
);
}
How can I get rid of that empty space in StrategyDetails screen as I'm dealing with nested navigators...
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',
})}
/>
App.tsx
const Tab = createMaterialTopTabNavigator();
export default function App() {
const [text, setText] = useState('');
const [search, setSearch] = useState('');
const searchHandler = () => {
setSearch(text)
}
return (
<NavigationContainer>
<View style={styles.case1}></View>
<View style={styles.case1}>
<TextInput
style={styles.input}
placeholder='search'
onChangeText={(val) => setText(val)}
/>
<Button
onPress={searchHandler}
title="search button"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>
</View>
<Tab.Navigator>
<Tab.Screen name="google1" component={() => <Google item={search} />} />
<Tab.Screen name="google2" component={() => <Google2 item={search} />} />
</Tab.Navigator>
</NavigationContainer>
);
}
Google.tsx
export default function google(props) {
return (
<View style={styles.webContainer}>
<WebView
source={{
uri:
'https://www.google.com/search?hl=en&sxsrf=ALeKk03BNJhsVEURZyXdlkOpk1l1qb2Nug%3A1595674787434&source=hp&ei=oxAcX4qzGLLxhwOhw5jwBA&q=' + props.item + '&oq=' +** props.item ** +'&gs_lcp=CgZwc3ktYWIQAzIFCC4QkwIyAggAMgIIADICCC4yAggAMgIIADICCAAyAggAMgIIADICCAA6BAgjECc6BQguEJECOgUIABCRAjoICC4QxwEQowI6BwguEEMQkwI6CAguEMcBEK8BUPEDWOkHYKwIaABwAHgAgAGMAYgBigOSAQMwLjOYAQCgAQGqAQdnd3Mtd2l6&sclient=psy-ab&ved=0ahUKEwjKkIXnn-jqAhWy-GEKHaEhBk4Q4dUDCAc&uact=5'
}}
/>
</View>
);
};
I'm creating an app that searches across different sites by typing in the search bar.
Whenever I type in the web-view keeps re-rendering. I only want to be re-rendered(Google web-view) when I click the search button.
I've heard that should use use-memo, Callback, as far as I know.
Thanks
const searchHandler = () => {
setSearch(text) <- "text" is a reference
}
try
const searchHandler = () => {
setSearch(`${text}`)
}
Prevent rerender WebView from props through useMemo. (memoization)
export default function google(props) {
const Web = React.useMemo(() => {
return <WebView source={{uri: 'abc'}} />
}, [])
return (
<View style={styles.webContainer}>
{Web}
</View>
);
};
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>
)
};
};