Renderer more hooks than during the previous render - reactjs

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.

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 }
]
...

Implicit parameters in React Native

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.

onPress function for DrawerNavigator

I have created a DrawerNavigator in my react native app which looks like this.
I just dont like the default header that react- native gives. So I wanna access it through an icon. I guess also using the onPress condition
import { createStackNavigator } from '#react-navigation/stack';
import { createDrawerNavigator } from '#react-navigation/drawer';
// importing of all screens
const Drawer = createDrawerNavigator();
const DrawerContent = () => {
return (
<Drawer.Navigator>
<Drawer.Screen
name="Home"
component={CategoryStack}
/>
<Drawer.Screen name="Aboutus" component={Aboutus} />
<Drawer.Screen name="Interest Recieved" component={InterestRecieved} />
</Drawer.Navigator>
);
};
const Stack = createStackNavigator();
const MainStack = () => {
return (
<Stack.Navigator>
<Stack.Screen
name="Loading"
component={Loading}
options={{ headerShown: false }}
/>
</Stack.Navigator>
);
};
export default MainStack;
How do I open it using an onPress of an icon?
Thanks!
React navigation useNavigation hook expose drawer actions - toggleDrawer,openDrawer and closeDrawer event handlers which you can use to open or close drawer.
import React from "react";
import { View, Text, StyleSheet, Pressable } from "react-native";
import { useNavigation } from "#react-navigation/native";
const ToggleDrawer = () => {
const { toggleDrawer,closeDrawer,openDrawer } = useNavigation();
return (
<Pressable onPress={toggleDrawer}>{/** Add your Icon Here */}</Pressable>
);
};
You can check in-depth drawer example
In the header options you can customize the header and add for example an icon on the top left side like this.
useLayoutEffect(() => {
navigation.setOptions({
title: 'ScreenName',
headerLeft: () => (
<View style={{ marginLeft: 15 }}>
<TouchableOpacity onPress={() => navigation.openDrawer()} >
{*INSERT ICON HERE*}
</TouchableOpacity>
</View>
),
})
})

Testing: react-native (expo) take snapshot of a Screen component inside of react-navigation

My test attempt currently looks like this:
import React from "react"
import { NavigationContainer } from "#react-navigation/native"
import { createStackNavigator } from "#react-navigation/stack"
import { render, waitFor } from "#testing-library/react-native"
import { Provider } from "react-redux"
import { createStore } from "redux"
import rootReducer from "src/store/reducers/root-reducer"
import ProfileScreen from "src/screens/profile"
const store = createStore(rootReducer)
test("renders correctly", async () => {
const Screen = () => {
// const navigationRef = React.useRef(null) // cause "Invalid hook call. Hooks can only be called inside .."
const Stack = createStackNavigator()
return (
<Provider store={store}>
<NavigationContainer
// ref={navigationRef} // cause "Invalid hook call. Hooks can only be called inside .."
>
<Stack.Navigator>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
</Provider>
)
}
const tree = render(<Screen />)
await waitFor(async () => expect(tree.toJSON()).toMatchSnapshot())
})
But it keeps throwing this:
...
console.error
The above error occurred in the <ForwardRef(NavigationContainer)> component:
in Provider (created by Screen)
in Screen
Consider adding an error boundary to your tree to customize error handling behavior.
Visit <fb link> to learn more about error boundaries
...
I can't find any useful docs/examples on the react-navigation site regrading testing a screen. (I also googled a lot around)
I can try to boil down the code to something like this:
App.json
...
export default function App() {
...
return (
<Provider store={store}>
<Navigation />
</Provider>
)
}
Navigation.js
import React from "react"
import { NavigationContainer, DarkTheme } from "#react-navigation/native"
import { createStackNavigator } from "#react-navigation/stack"
import BottomTabNavigator from "./BottomTabNavigator"
export default function Navigation() {
const navigationRef = React.useRef(null)
return (
<NavigationContainer
theme={DarkTheme}
ref={navigationRef} // https://reactnavigation.org/docs/navigation-container/#ref
>
<RootNavigator />
</NavigationContainer>
)
}
const Stack = createStackNavigator()
function RootNavigator() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Root" component={BottomTabNavigator} />
...
</Stack.Navigator>
)
}
BottomTabNavigator
import React from "react"
import { Linking, TouchableWithoutFeedback, View } from "react-native"
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs"
import { createStackNavigator } from "#react-navigation/stack"
import { Feather } from "#expo/vector-icons"
import Profile from "src/screens/profile"
import SignIn from "src/screens/sign-in"
const BottomTab = createBottomTabNavigator()
export default function BottomTabNavigator() {
const TabIcon = ({ name, color }) => <Feather name={name} color={color} size={20} />
return (
<BottomTab.Navigator
tabBarOptions={{
activeTintColor: Colors.defaultWhite,
style,
}}
>
<BottomTab.Screen
name="Profile"
component={ProfileNavigator}
options={{
tabBarIcon: ({ color }) => <TabIcon name="user" color={color} />,
title: I18n.t("components.tab_navigator.profile"),
}}
/>
)
}
const ProfileStack = createStackNavigator()
function ProfileNavigator() {
return (
<ProfileStack.Navigator>
<ProfileStack.Screen name="Profile" component={Profile} options={{ headerShown: false }} />
<ProfileStack.Screen name="SignIn" component={SignIn} options={{ headerShown: false }} />
</ProfileStack.Navigator>
)
}

How to use rtl layout of material-ui next in react app

I want to use rtl layout in my react application. I have used material-ui next version to integrate this application. I have used below code to make application layout rtl. Some components work properly in the rtl layout but some components doesn't affected.
/**
* App.js Layout Start Here
*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { MuiThemeProvider } from 'material-ui/styles';
import { IntersectingCirclesSpinner } from 'react-epic-spinners';
import { IntlProvider } from 'react-intl';
import { Redirect, Route } from 'react-router-dom';
import { NotificationContainer } from 'react-notifications';
// app routes
import Dashboard from '../routes/dashboard';
import AppSignUp from '../routes/AppSignUp';
// App locale
import AppLocale from '../lang';
// themes
import lightTheme from './themes/lightTheme';
import darkTheme from './themes/darkTheme';
class App extends Component {
state = {
loading: true
}
componentDidMount() {
let self = this;
setTimeout(() => {
self.setState({ loading: false });
}, 1000);
}
render() {
const { locale, darkMode, rtlLayout } = this.props.settings;
if (this.state.loading) {
return (
<div className="d-flex justify-content-center">
<IntersectingCirclesSpinner color="red" className="rct-loader" />
</div>
);
}
const currentAppLocale = AppLocale[locale.locale];
let theme = '';
if (darkMode) {
theme = darkTheme
} else {
theme = lightTheme
}
if (rtlLayout) {
theme.direction = 'rtl'
} else {
theme.direction = 'ltr'
}
return (
<MuiThemeProvider theme={theme}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<React.Fragment>
<NotificationContainer />
<Route path="/dashboard" component={Dashboard} />
<Route path="/signup" component={AppSignUp} />
</React.Fragment>
</IntlProvider>
</MuiThemeProvider>
);
}
}
// map state to props
const mapStateToProps = ({ settings, authUser }) => {
const { user } = authUser;
return { settings, user };
};
export default connect(mapStateToProps)(App);
It doesn't work properly also i have added
<html dir="rtl">...</html>
(1) Don't mutate the theme directly, use getMuiTheme instead:
themeWithDirection = getMuiTheme(theme, { direction: 'rtl' });
Based on: https://github.com/mui-org/material-ui/issues/1926#issuecomment-192736335
(2) Create the RTL component as shown in the Material-UI documentation and put it around your root component:
function RTL(props) {
return (
<JssProvider jss={jss} generateClassName={generateClassName}>
{props.children}
</JssProvider>
);
}
return (
<RTL>
<MuiThemeProvider theme={themeWithDirection}>
{/* your component code */}
</MuiThemeProvider>
</RTL>
);
Props to this answer for explicitly showing what to do with the RTL function.

Resources