Implicit parameters in React Native - reactjs

Hi everyone I have a doubt regarding the following Code.
import { StatusBar as ExpoStatusBar } from "expo-status-bar";
import React from "react";
import { ThemeProvider } from "styled-components/native";
import { Text } from "react-native";
import { Ionicons } from "#expo/vector-icons";
import { NavigationContainer } from "#react-navigation/native";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import {
useFonts as useOswald,
Oswald_400Regular,
} from "#expo-google-fonts/oswald";
import { useFonts as useLato, Lato_400Regular } from "#expo-google-fonts/lato";
import { theme } from "./src/infrastructure/theme";
import { RestaurantsScreen } from "./src/features/restaurants/screen/restaurants.screen";
import { SafeArea } from "./src/components/utility/safe-area.component";
import { RestaurantsContextProvider } from "./src/services/restaurants/restaurants.context";
const Tab = createBottomTabNavigator();
const TAB_ICON = {
Restaurants: "md-restaurant",
Map: "md-map",
Settings: "md-settings",
};
const createScreenOptions = ({ route }) => {
const iconName = TAB_ICON[route.name];
return {
tabBarIcon: ({ size, color }) => (
<Ionicons name={iconName} size={size} color={color} />
),
};
};
const Settings = () => (
<SafeArea>
<Text>Settings</Text>
</SafeArea>
);
const Map = () => (
<SafeArea>
<Text>Map</Text>
</SafeArea>
);
export default function App() {
const [oswaldLoaded] = useOswald({
Oswald_400Regular,
});
const [latoLoaded] = useLato({
Lato_400Regular,
});
if (!oswaldLoaded || !latoLoaded) {
return null;
}
return (
<>
<ThemeProvider theme={theme}>
<RestaurantsContextProvider>
<NavigationContainer>
<Tab.Navigator
screenOptions={createScreenOptions}
tabBarOptions={{
activeTintColor: "tomato",
inactiveTintColor: "gray",
}}
>
<Tab.Screen name="Restaurants" component={RestaurantsScreen} />
<Tab.Screen name="Map" component={Map} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
</NavigationContainer>
</RestaurantsContextProvider>
</ThemeProvider>
<ExpoStatusBar style="auto" />
</>
);
}
In particular form my comprehension I assumed that the function createScreenOptions
const createScreenOptions = ({ route }) => {
const iconName = TAB_ICON[route.name];
return {
tabBarIcon: ({ size, color }) => (
<Ionicons name={iconName} size={size} color={color} />
),
};
};
that is called here :
<Tab.Navigator
screenOptions={createScreenOptions}
tabBarOptions={{
activeTintColor: "tomato",
inactiveTintColor: "gray",
}}
>
uses the parameter route .
I have a doubt regarding the following code. In particular, I assume that the function createScreenOptions uses the parameter route, but I am unable to figure out how it is passed to the function. Can someone explain how the route parameter is passed to the createScreenOptions function?
Thank you so much.

This is a common pattern with functions passed to props. The components have information at runtime that you need; you want to do something at runtime that depends on that information. You can't know that information ahead of time! Only the component will know that, at runtime. That's why you give the component a function to run, and the component runs the function with that information.
An easy case to think about is the onChangeText prop of a TextInput. You want to have the text input do something when that text changes. The prop has this signature:
onChangeText?: ((text: string) => void)
so you'd give it a callback like
<TextInput
onChangeText={(text) => console.log(text)}
Let's say that internally, the text input stores your function in a variable called onChangeText. The text input knows what text it has inside of it and when it changes. Whenever it changes, it runs onChangeText(text), and you get your log.
The same is true for react-navigation. It knows what route it's on at runtime. The screenOptions prop has this signature:
screenOptions?: ScreenOptions | ((props: {
route: RouteProp<ParamList>;
navigation: any;
}) => ScreenOptions)
which tells us that you can give it an object of type ScreenOptions, or a function that takes in the route and navigation from the library, and returns a ScreenOptions object.
If you give the prop a function, like you have, ideally it runs that function when it's needed and with the information the function needs.

Related

React native eslint-disable-next-line react/no-unstable-nested-components

I am using a custom back button in the headerLeft. I have enabled ESLINT, and it is showing the below warning.
Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “App” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true.eslintreact/no-unstable-nested-components
App.tsx
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
*/
import React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createDrawerNavigator} from '#react-navigation/drawer';
import {StatusBar, useColorScheme} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {useNavigation} from '#react-navigation/native';
import {DrawerContent} from './src/navigation/drawer-content';
import {Button} from 'react-native-paper';
import Feed from './src/screens/feed';
import Article from './src/screens/article';
const Drawer = createDrawerNavigator();
const CustomDrawer = (props: any) => {
return <DrawerContent {...props} />;
};
const BackButton = () => {
const navigation = useNavigation();
return (
<Button
icon="arrow-left-thick"
onPress={() => navigation.goBack()}
children={undefined}
/>
);
};
function App(): JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<NavigationContainer>
{/* <SafeAreaView style={backgroundStyle}> */}
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<Drawer.Navigator drawerContent={CustomDrawer} initialRouteName="Feed">
<Drawer.Screen name="Feed" component={Feed} />
<Drawer.Screen
name="Article"
component={Article}
options={{
title: 'Timeline',
headerLeft: () => <BackButton />,
}}
/>
</Drawer.Navigator>
</NavigationContainer>
);
}
export default App;
It could be because of the following eslint rule react/no-unstable-nested-components.
I guess according to react-navigation's guide you have defined the headerLeft prop but the above eslint rule contradicts that.
Contradiction occurred here:
<Drawer.Screen
name="Article"
component={Article}
options={{
title: 'Timeline',
headerLeft: () => <BackButton />, // <--- this line
}}
/>
Possible fix(es):
Disable plugin for the file: // eslint-disable react/no-unstable-nested-components (or for just one line)
Move component definition out of the component.
Edit the .eslintrc to allow component as props in this rule's settings
...
"react/no-unstable-nested-components": [
"off" | "warn" | "error",
{ "allowAsProps": true | false }
]
...

Renderer more hooks than during the previous render

I'm working on a project with bare expo and react native. I have implemented firebase to my project. The signIn and register are working fine. I have created a auth check to send the user to another page in case he is logged in. Although I am receiving the following error when attempting to send the user to another page:
This is my App.tsx:
import React, { useEffect, useState } from 'react';
import { StatusBar } from 'expo-status-bar';
import { ThemeProvider } from 'styled-components';
import {
useFonts,
Poppins_400Regular,
Poppins_500Medium,
Poppins_700Bold
} from '#expo-google-fonts/poppins';
import AppLoading from 'expo-app-loading';
import theme from './src/global/styles/theme';
import { NavigationContainer } from '#react-navigation/native';
import { AppRoutes } from './src/routes/app.routes';
import 'intl';
import 'intl/locale-data/jsonp/pt-BR';
import { SignIn } from './src/screens/SignIn';
import auth, { FirebaseAuthTypes } from '#react-native-firebase/auth';
export default function App() {
const [ fontsLoaded ] = useFonts({
Poppins_400Regular,
Poppins_500Medium,
Poppins_700Bold
});
const [user, setUser] = useState<FirebaseAuthTypes.User | null>(null);
if (!fontsLoaded) {
return <AppLoading />;
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(setUser);
return subscriber;
}, []);
return (
<ThemeProvider theme={theme}>
<StatusBar style="light"/>
<NavigationContainer>
{ user ? <AppRoutes /> : <SignIn /> }
</NavigationContainer>
</ThemeProvider>
);
}
This is the routes code:
import React from 'react';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { Dashboard } from '../screens/Dashboard';
import { Register } from '../screens/Register';
import { useTheme } from 'styled-components';
import { Platform } from 'react-native';
import { MaterialIcons } from '#expo/vector-icons';
import { Summary } from '../screens/Summary';
const { Navigator, Screen } = createBottomTabNavigator();
export function AppRoutes() {
const theme = useTheme();
return (
<Navigator
screenOptions={{
headerShown: false,
tabBarActiveTintColor: theme.colors.orange,
tabBarInactiveTintColor: theme.colors.texts,
tabBarLabelPosition: 'beside-icon',
tabBarStyle: {
height: 88,
paddingVertical: Platform.OS === 'ios' ? 20 : 0
}
}}
>
<Screen
key="Listing"
name="Listing"
component={Dashboard}
options={{
tabBarIcon: (({ size, color }) =>
<MaterialIcons
name="format-list-bulleted"
size={size}
color={color}
/>
)
}}
/>
<Screen
key="Register"
name="Register"
component={Register}
options={{
tabBarIcon: (({ size, color }) =>
<MaterialIcons
name="attach-money"
size={size}
color={color}
/>
)
}}
/>
<Screen
key="Summary"
name="Summary"
component={Summary}
options={{
tabBarIcon: (({ size, color }) =>
<MaterialIcons
name="pie-chart"
size={size}
color={color}
/>
)
}}
/>
</Navigator>
);
}
Your issue comes from this block:
if (!fontsLoaded) {
return <AppLoading />;
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(setUser);
return subscriber;
}, []);
You are conditionally adding a hook here, because if fontsLoaded evaluates to false, you return the render function earlier and the useEffect hook is never run at the first render. However, in subsequent attempts, the fonts are probably loaded, and this causes a different number of hooks being rendered: hence the error you get.
Based on react's own "rules of hooks" guide, emphasis my own:
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders.
To fix this, simply ensure all hooks are defined before any return paths. In your case, this is done by swapping the fontsLoaded check:
// Define all hooks before any return paths!
useEffect(() => {
const subscriber = auth().onAuthStateChanged(setUser);
return subscriber;
}, []);
if (!fontsLoaded) {
return <AppLoading />;
}
return (
<ThemeProvider theme={theme}>
<StatusBar style="light"/>
<NavigationContainer>
{ user ? <AppRoutes /> : <SignIn /> }
</NavigationContainer>
</ThemeProvider>
);
It is of course sometimes difficult to catch errors like this, especially in a heavy/complicated component. If you're using eslint, there is an eslint plugin called eslint-plugin-react-hook that will cause eslint to throw a warning/error when this happens.

Navigation with parameters from custom element in Flatlist in React Native: Error: Invalid hook call

I am new to react native and have a problem figuring out how to navigate from one class to another one with passing parameters and would appreciate your help.
All I want to do is:
ClassA should have a checkbox with state handling and a flatlist containing CustomButton
Navigate from ClassA to TargetScreen by clicking CustomButton
Pass parameter "element" to TargetScreen
Show content of parameter passed in TargetScreen
The error message I get:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and
fix this problem.
ClassA:
import React, { Component, useState } from 'react';
import { useNavigation } from '#react-navigation/native';
import { CustomButton} from './CustomButton.js';
import { CheckBox, SafeAreaView, FlatList} from 'react-native';
class ClassA extends React.Component {
render() {
const [ASelected, setA] = useState(false);
const NavigateWithParams = () => {
navigation = useNavigation();
this.props.navigation.navigate('TargetScreen', { element: 'elementname' })
}
const renderItemCustom= ({ item }) => (
<CustomButton onPress={() => navigateWithParams()} />
);
}
return (
<CustomConst/>
<CheckBox value={ASelected}
onValueChange={{setA}} />
<SafeAreaView>
<FlatList
data={data}
renderItem={renderItemCustom}
keyExtractor={(item) => item.element}
/>
</SafeAreaView>
);
}
export default ClassA;
TargetScreen:
class TargetScreen extends React.Component {
render() {
const { navigation } = this.props;
return (
<Text> {JSON.stringify(navigation.getParam('element'))} </Text>
);
}
}
export default TargetScreen;
+++++++++++++++++++++++++
Update:
As of now the code looks like this:
class ClassA extends React.Component {
NavigateWithParams = (element) => {
this.props.navigation.navigate('TargetScreen', { element: 'elementname' })
}
renderItemCustom = ({ item }) => (
<CustomButton element={item.title} onPress={() => this.NavigateWithParams(item.element)} />
);
render() {
return (
<SafeAreaView>
<FlatList
data={data}
renderItem={this.renderItemCustom}
keyExtractor={(item) => item.id}
/>
</SafeAreaView>
);
}
}
export default ClassA;
And I am now getting this issue:
TypeError: Cannot read property 'navigate' of undefined
+++++++++++++++++++++++++
Update2
Routing:
function ClassA({ navigation }) {
return (
<ClassAScreen/>
);
function Target({ navigation }) {
return (
<TargetScreen/>
);
//navigation stacks
const SessionStack = createStackNavigator();
function SessionStackScreen({ navigation }) {
return (
<SessionStack.Navigator>
<SessionStack.Screen
name="ClassA"
component={ClassA}
options={{ tabBarLabel: 'ClassA!', headerShown: false }}
/>
<SessionStack.Screen
name="Target"
component={Target}
options={{ tabBarLabel: 'works!' }}
/>
</SessionStack.Navigator>
)
}
Logging gives me this:
renderItemCustom = ({ item }) => (
<CustomButton element={item.title} onPress={() => console.log(this.props)} />
);
+++++++++++++++++
Update:
Solution can be found here:
Navigation with parameters from custom element in Flatlist in React Native: Empty parameters
You cant use hooks inside a class component so remove the line which has the hook
and change like below
const NavigateWithParams = element => {
this.props.navigation.navigate('TargetScreen', { element: element })
}
const renderItemCustom= ({ item }) => (
<CustomButton onPress={() => this.navigateWithParams(item.element)} />
);
And parameter are passed using the route prop
class TargetScreen extends React.Component {
render() {
const { route} = this.props;
return (
<Text> {JSON.stringify(route.params.element)} </Text>
);
}
}
Also for the checkbox instead of using the useState hook, use this.state.
You can’t use Hooks inside a class component
https://reactjs.org/docs/hooks-faq.html#should-i-use-hooks-classes-or-a-mix-of-both
UPDATE:
Work variant, you can try here: https://snack.expo.io/#vasylnahuliak/stackoverflow-67862370
import 'react-native-gesture-handler';
import React from 'react';
import { Text, View, StyleSheet, Button, FlatList } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const DATA = [
{
id: 0,
title: 'first button',
element: 'something'
},
{
id: 1,
title: 'second button',
element: 'another something'
},
]
const HomeSceen = ({ route, navigation }) => {
return (
<View>
<Text>{JSON.stringify(route, null, 2)}</Text>
<Button
title="Navigate to ProfileScreen"
onPress={() => {
navigation.navigate('ProfileScreen');
}}
/>
</View>
);
};
const ProfileScreen = ({ route, navigation }) => {
const NavigateWithParams = (element) => {
navigation.navigate('HomeSceen', { element });
};
const renderItemCustom = ({ item }) => (
<Button title={item.title} onPress={() => NavigateWithParams(item.element)} />
);
return (
<View>
<Text>{JSON.stringify(route, null, 2)}</Text>
<FlatList
data={DATA}
renderItem={renderItemCustom}
keyExtractor={(item) => item.id}
/>
<Button
title="Navigate to HomeSceen"
color="tomato"
onPress={() => {
navigation.navigate('HomeSceen');
}}
/>
</View>
);
};
const SessionStack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<SessionStack.Navigator>
<SessionStack.Screen name="HomeSceen" component={HomeSceen} />
<SessionStack.Screen name="ProfileScreen" component={ProfileScreen} />
</SessionStack.Navigator>
</NavigationContainer>
);
};
export default App;

expo icons on the bottom tab navigator are not showing

I'm currently doing a react-native course and I started implementing my own code for the styling part. I have an issue with icons not showing and I can't seem to find a problem. I have been trying to implement the icon on the bookcase tab, but it doesn't appear on it, and also I don't get an error message on the expo.
import React from 'react';
import { createBottomTabNavigator } from 'react-navigation-tabs'
import { createAppContainer, createSwitchNavigator} from 'react-navigation';
import { View, StyleSheet } from 'react-native';
import { Text, Button, Input } from 'react-native-elements';
import {createStackNavigator} from 'react-navigation-stack';
import { Ionicons } from '#expo/vector-icons';
const switchNavigator = createSwitchNavigator({
ResolveAuth: ResolveAuthScreen,
loginFlow: createStackNavigator ({
Signin: signin,
Signup: signup,
}),
mainFlow: createBottomTabNavigator({
Bookcase: bookcase,
Explore: explore,
Profile: profile
}, {
tabBarOptions: {
activeTintColor: 'red',
inactiveTintColor: 'grey',
showIcon: true,
}},
{
Bookcase:{
screen:bookcase,
navigationOptions:{
tabBarLabel:'Bookcase',
tabBarIcon:({ tintColor })=>(
<Ionicons name="ios-home" color={tintColor} size={2}/>
)
}
}
}
)
});
const App = createAppContainer(switchNavigator)
export default () => {
return (
<AuthProvider>
<App ref ={(navigator) => {setNavigator(navigator)}} />
</AuthProvider>
);
};
If you are using react-navigation v5, then is preferred way to implement bottomStackNavigation. This code will render icons and its color according to active as well as inactive state. You have to pass options property to individual screen tab in react-navigation v5.
const BottomNavigator = () => {
const BottomNavigation = createBottomTabNavigator();
return (
<BottomNavigation.Navigator>
<BottomNavigation.Screen
name="Home"
component={HomeStack}
options={{
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="home" color={color} size={size} />
),
}}
/>
<BottomNavigation.Screen
name="Exam"
component={ExamStack}
options={{
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name="book" color={color} size={size} />
),
}}
/>
</BottomNavigation.Navigator>
);
};

Override Header Title React Navigation V5

I have created a nested tab navigator in my stack navigator as follows:
const Stack = createStackNavigator()
const Tab = createBottomTabNavigator();
function TabNav () {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen}></Tab.Screen>
<Tab.Screen name="Category" component={CategoryScreen}></Tab.Screen>
<Tab.Screen name="Settings" component={SettingsScreen}></Tab.Screen>
</Tab.Navigator>
)
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Tab" component={TabNav}></Stack.Screen>
<Stack.Screen name="Category" component={CategoryScreen}>
</Stack.Screen>
<Stack.Screen
name="SpecialBeerScreen" component= {SpecialBeerScreen}>
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)
}
However, the header now looks like Tab for each of the screens. How do I override this Header for each of the screens with a custom text, such as Home, Category etc.
Update: This is still valid in react-navigation v6
Use getFocusedRouteNameFromRoute as outlined in the react-navigation docs in the section Setting parent screen options based on child navigator's state to access information of screens in nested navigators.
Change this line
<Stack.Screen name="Tab" component={TabNav}></Stack.Screen>
to the following (adding the options prop):
<Stack.Screen
name="Tab"
component={TabNav}
options={({ route }) => {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';
switch (routeName) {
case 'Category': {
return {
headerTitle: 'Category',
};
}
case 'Settings': {
return {
headerTitle: 'Settings',
};
}
case 'Home':
default: {
return {
headerTitle: 'Home',
};
}
}
}}
/>
You also have to import getFocusedRouteNameFromRoute, so also add:
import { getFocusedRouteNameFromRoute } from '#react-navigation/native';
If I understand your problem correctly, you wanted to change the stack title when the tab is changed. In this case, you may use React Context to control it.
(I also put this code in snack https://snack.expo.io/#gie3d/change-header-from-tab)
edit: 1, I separated it into 3 files and assume that it's all in the same directory.
HomeTitleContext.js
export const HomeTitleContext = React.createContext({
title: 'default title',
setTitle: () => {},
});
App.js
import { HomeTitleContext } from './HomeTitleContext';
export default function App() {
const [title, setTitle] = useState('default title');
return (
<HomeTitleContext.Provider
value={{
title,
setTitle,
}}
>
<HomeTitleContext.Consumer>
{(ctx) => (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Tab"
component={TabNav}
options={{ title: ctx.title }} // The title will be dynamically changed here
/>
<Stack.Screen
name="Category"
component={OtherScreen}
></Stack.Screen>
<Stack.Screen
name="SpecialBeerScreen"
component={OtherScreen}
></Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
)}
</HomeTitleContext.Consumer>
</HomeTitleContext.Provider>
);
}
In your component, for example: HomeScreen, you set up a useFocusEffect and change the title from setTitle which you'll get from the context
HomeScreen.js
import React, { useContext } from 'react';
import { useFocusEffect } from '#react-navigation/native';
import { View, Text } from 'react-native';
import { HomeTitleContext } from './HomeTitleContext';
const HomeScreen = ({ navigation }) => {
const { setTitle } = useContext(HomeTitleContext);
useFocusEffect(() => {
setTitle('this is home');
});
return (
<View>
<Text>Home</Text>
</View>
);
};

Resources