React Native : Rendered more hooks than during the previous render - reactjs

I am getting this error after implementing the gethomeid useState and cant understand I tried different things but nothing worked
import React, {useEffect, useState} from 'react';
import {
StyleSheet,
View,
Image,
Text,
TouchableOpacity,
Button,
} from 'react-native';
import {windowWidth, windowHeight} from '../utils/Dimentions';
//components
import EmptyContainer from '../components/EmptyContainer';
import Header from '../components/Header';
import TopTabs from '../components/Tabs';
//redux
import {getHomeData} from '../action/homedata';
import {connect} from 'react-redux';
import propTypes from 'prop-types';
// to render empty container
const Home = ({getHomeData, homeState, userDetails}) => {
const [gethomeid, setGethomeid] = useState(null);
if (userDetails) {
useEffect(() => {
getHomeData('123456');
}, []);
}
if (homeState.data != null && userDetails && gethomeid != null) {
console.log(homeState.data);
return (
<View style={{flex: 1}}>
<Header username={userDetails.name.split(' ')[0]} />
<TopTabs roomdata={homeState.data.roomMapper} />
</View>
);
} else {
return (
<View>
<EmptyContainer />
</View>
);
}
};
const mapStateToProps = state => ({
homeState: state.data,
userDetails: state.auth.user,
});
const mapDispatchToProps = {
getHomeData,
};
Home.propTypes = {
getHomeData: propTypes.func.isRequired,
homeState: propTypes.object.isRequired,
userDetails: propTypes.object,
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
I am trying to load 2 buttons that will pass the data on clicked to the gethomeid but this error i am not able to solve
THE SNAPSHOT OF THE ERROR:-

You are conditionally calling the useEffect hook. This breaks the rules of hooks. You can likely move the conditional into the hook callback.
useEffect(() => {
if (userDetails) {
getHomeData('123456');
}
}, []);
You should probably also add any missing dependencies the linter may complain about, like userDetails and getHomeData.
useEffect(() => {
if (userDetails) {
getHomeData('123456');
}
}, [getHomeData, userDetails]);

Related

Use hook in export function - React Native

I want to write 1 common function that I will use in different functional components.
This generic function uses hooks and I get the error: Error: Invalid hook call. Hooks can only be called inside the body of a functional component.
Example of my code:
App.js
import React from 'react';
import {
Text,
TouchableOpacity,
} from 'react-native';
import { Change } from 'static/Change';
export default function App() {
return (
<TouchableOpacity
onPress={() => {
Change();
}}
>
<Text>Click Me!</Text>
</TouchableOpacity>
);
}
Change.js
import React from 'react';
export const Change = () => {
const [State, setState] = React.useState(0);
// Other hook work.
// The function returns nothing
};
What is my mistake and how can I fix it?
Hooks has some rules to follow - https://reactjs.org/docs/hooks-rules.html
Refactor code as below
import React from "react";
import { Text, TouchableOpacity } from "react-native";
function useChange() {
const [state, setState] = React.useState(0);
function change(value) {
setState(value);
}
return { change, state };
}
export default function App() {
const { change, state } = useChange();
return (
<TouchableOpacity
onPress={() => {
// Update state value on press
change(state + 1);
}}
>
<Text>Click Me!{state}</Text>
</TouchableOpacity>
);
}

My react hooks fail when used inside my react component that is deeply nesed into my react application

When I use useHistory hook here ...
import React from 'react';
import {View,Image,StyleSheet, TouchableOpacity} from 'react-native';
import theme from '../theme';
import Text from './Text'
import {useQuery} from '#apollo/react-hooks'
import {GET_REPOSITORY} from '../graphql/queries'
import {useHistory} from 'react-router-native'
const RepositoryItem = (props) => {
let history = useHistory()
let item = props.item
const styles = StyleSheet.create({
...
});
const reduceToK = (num) => {
if (num > 999) {
const thousandsNum = Math.round(num / 1000);
const remainerNum = Math.round((num % 1000) / 100);
return `${thousandsNum}.${remainerNum}k`;
} else return num;
};
return (
<View style={styles.rootContainer}>
{...}
</View>
);
};
export default RepositoryItem;
The application breaks and returns the following 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: 1. You might have mismatching versions of React and the renderer (such as React DOM)2. You might be breaking the Rules of Hooks3. You might have more than one copy of React in the same app
This component (RepositoryItem) is being used inside another component (RepositoryList) like so ...
import React from 'react';
import { FlatList, View, StyleSheet, Text } from 'react-native';
import RepositoryItem from './RepositoryItem';
// ...
const styles = StyleSheet.create({
...
});
const RepositoryList = () => {
let history = useHistory()
const repositories = useQuery(GET_REPOSITORIES, {fetchPolicy: 'cache-and-network'});
if (!repositories.loading) {
const ItemSeparator = () => <View style={styles.separator} />;
// Get the nodes from the edges array
const repositoryNodes = repositories.data.repositories && !repositories.loading
? repositories.data.repositories.edges.map(edge => edge.node)
: [];
return (
<FlatList
data={repositoryNodes}
ItemSeparatorComponent={ItemSeparator}
renderItem={RepositoryItem}
keyExtractor={repository => repository.fullName}
/>
);
} else {
return (<View><Text>Loading...</Text></View>);
}
};
export default RepositoryList;
That component is being used in the App component.
My theory is that in react native's FlatList renderItem prop one cannot use hooks.
Any idea what the issue might be here?
You may want to do it this way because renderItem accepts a function.
<FlatList
data={repositoryNodes}
ItemSeparatorComponent={ItemSeparator}
renderItem={({item}) => <RepositoryItem item={item} />}
keyExtractor={repository => repository.fullName}
/>
change
import {useHistory} from 'react-router-native'
t0
import {useHistory} from 'react-router-dom'
based on react-router-native documentation

React native typescript: usecontext functions not firing from inside child component

I have an issue when i try to use functions from a context inside a child component in a React native android app.
Below is my code for the context, and the form component im using it in (stripped down for brevity).
The "isFormOpen" object can be read no problem from inside any children that is wrapped in the provider, but when i try to call the "toggleForm" function from the same child component, it does nothing, no console errors either.
I have another context which is identical in structure and syntax except for vairable and function names etc, and that works perfectly, so im a bit confused as to why this does not work. I removed the other context, thinking there might be some type of conflict, but didnt solve it.
AccountContext.tsx
import React, { FC, createContext, useContext, useState } from 'react';
interface AccountContextType {
isFormOpen: boolean,
toggleForm: (toggle: boolean) => void
};
export const AccountContext = createContext<AccountContextType>({
isFormOpen: false,
toggleForm: () => null
});
export const AccountContextProvider: FC = props => {
const [formOpen, setFormOpen] = useState<boolean>(false);
const toggleForm = (toggle: boolean) => {
setFormOpen(toggle);
}
const value: AccountContextType = {
isFormOpen: formOpen,
toggleForm
}
return (
<AccountContext.Provider value={value}>
{props.children}
</AccountContext.Provider>
)
}
export const useAccountContext = () => useContext(AccountContext);
TrackUploadForm.js
import React from 'react';
import { SafeAreaView } from 'react-native';
import { Button } from 'react-native-paper';
import { useAccountContext } from '../contexts/AccountContext';
import { AccountContextProvider } from '../contexts/AccountContext';
const TrackUploadForm = () => {
const accountContext = useAccountContext();
return (
<AccountContextProvider>
<SafeAreaView>
<Button onPress={() => accountContext.toggleForm(false)} mode='outlined'>Cancel</Button>
</SafeAreaView>
</AccountContextProvider>
)
};
export default TrackUploadForm;
useAccountContext is called outside the provider
export default function App() {
return (
<AccountContextProvider>
<Content />
</AccountContextProvider>
);
}
const Content = () => {
const accountContext = useAccountContext();
return (
<div className="App">
<h1>{accountContext.isFormOpen ? "true" : "false"}</h1>
<Button onPress={() => accountContext.toggleForm(false)} mode='outlined'>Cancel</Button>
</div>
);
};
accountContext.toggleForm(false) <-- always false, change it to accountContext.toggleForm(!accountContext.isFormOpen)
Together we have
https://codesandbox.io/s/cranky-panini-yo129

React Native Navigation: Check if drawer is opened or not

i have a component that is outside the Drawer tag but it's inside a NavigationContainer.
So i used useRef from react to get navigate method .
So i use this example: Navigating without navigation prop
But now, i can't get the state of the drawer (is it opened or closed).
Here's my App component:
import 'react-native-gesture-handler';
import React from 'react';
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { Provider } from "react-redux";
import GetInformationByLocation from "./reducers/GetInformationByLocation";
import Wrapper from './components/Wrapper';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { NavigationContainer } from '#react-navigation/native';
import GeoDisplay from "./components/GeoDisplay";
import { Text } from "react-native";
import { navigationRef } from "./helpers/Navigator";
const store = createStore(GetInformationByLocation, applyMiddleware(thunk));
/*
* TODO:
* 1. Wrap everything with parent component
* 2. Search bar
* 3. Save searched cities.
* 4. Check if the city is undefined and suggest nearest city.
* 5. Animated background
* 6. Fancy and animated font
* 7. Setup menu преди показване на приложението
*/
const Drawer = createDrawerNavigator();
const App = () => {
return (
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
<Wrapper />
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={GeoDisplay} />
</Drawer.Navigator>
</NavigationContainer>
</Provider >
);
}
export default App;
and my Wrapper.js Component:
import React, { useEffect, useState } from 'react';
import { StyleSheet, View, StatusBar } from 'react-native';
import { SearchBar, Header } from 'react-native-elements';
import { MainUI } from '../styling/UI';
import * as Navigator from "../helpers/Navigator";
import { DrawerActions } from "#react-navigation/native";
const Wrapper = (props) => {
const [search, setSearch] = useState(true);
const openDrawer = () => {
Navigator.navigationRef.current.dispatch(DrawerActions.openDrawer())
setSearch(false);
}
useEffect(() => {
}, []);
return (
<>
<StatusBar backgroundColor={"#011E25"} />
<Header
leftComponent={{ icon: 'menu', color: '#fff', onPress: () => { openDrawer() } }}
centerComponent={{ text: 'COVID Bulgaria', style: { color: '#fff' } }}
rightComponent={{ icon: 'home', color: '#fff' }}
backgroundColor={'#011E25'}
/>
{search &&
<View>
<SearchBar placeholder="Търси град" containerStyle={MainUI.searchContainer} />
</View>
}
</>
)
}
export default Wrapper;
I dispatch openDrawer() action with navigationRef
Navigator.navigationRef.current.dispatch(DrawerActions.openDrawer())
but can't get status of the drawer.
I've tried many ways but not work.
Thanks in advance.
You can check if drawer is open by getting the navigation state:
const state = navigationRef.current.getRootState();
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
The above code assumes that drawer is at root. If it's nested, you'll need to traverse the state to find the state object with type: 'drawer'.
It's not clear why you need to check it from the question. Normally you shouldn't need to check it. If you dispatch DrawerActions.openDrawer() if drawer is already open, nothing will happen. So the check is unnecessary. If you want to close the drawer if it was open, you can dispatch DrawerActions.toggleDrawer() instead.
You can create a helper class like:
class DrawerHandler() {
openDrawer() {
this.drawerOpened = true;
}
closeDrawer() {
this.drawerOpened = false;
}
getDrawerStatus() {
return this.drawerOpened;
}
}
export default new DrawerHandler();
Use open and close functions on drawer opening and closing and get function to get status of the drawer

How to fix memory leak issue in react native?

Problem:
My App component is giving me an error like this.
D:\projects\myapp\node_modules\react-native\Libraries\Core\ExceptionsManager.js:173 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in App (at renderApplication.js:45)
This is how my code looks like.
/* eslint-disable prettier/prettier */
import React, {Component} from 'react';
import {StatusBar} from 'react-native';
import {Provider} from 'react-redux';
import RootNavigator from '_navigations/RootNavigator';
import AsyncStorage from '#react-native-community/async-storage';
import store from '_store';
import Splashscreen from '_screens/splashscreen';
import axios from 'axios';
import refreshToken from '_store/actions/refreshToken';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
};
}
componentDidMount() {
this.updateLoading();
refreshToken();
}
updateLoading = async () => {
const data = await this.performTimeConsumingTask();
if (data !== null) {
this.setState({isLoading: false});
}
};
performTimeConsumingTask = async () => {
return new Promise((resolve) =>
setTimeout(() => {
resolve('result');
}, 3000),
);
};
render() {
if (this.state.isLoading) {
return <Splashscreen />;
}
return (
<>
<StatusBar barStyle="dark-content" />
<Provider store={store}>
<RootNavigator />
</Provider>
</>
);
}
}
export default App;
My splash screen is like this.
import React from 'react';
import {View, Image} from 'react-native';
import styles from './splashstyle';
import logo from '_assets/img/logo.png';
const Splashscreen = () => {
return (
<View style={styles.container}>
<Image source={logo} style={styles.logo} />
</View>
);
};
export default Splashscreen;
As it is saying the issue is inside rendered method but I do not where I have done wrong. I tried a lot to find out what where I have done wrong but it is giving me this issue over and over. Can someone help me to solve this issue?Thank you
if performTimeConsumingTask take so much time that the user moved to another screen(aka component) then the function updateLoading will continue its process which will try to setState in an unmounted component..
so you should cancel your work in componentWillUnmount to fix the issue
I would suggest some tweak like
class MyComponent{
constructor(){ ... this.isMounted = false; }
componentDidMount(){ ... this.isMounted = true; }
componentWillUnmount() { ... this.isMounted = false }
// then inside updateLoading
updateLoading = async () => {
const data = await this.performTimeConsumingTask();
if (this.isMounted && data !== null) {
// will protect set state against unmounted component
this.setState({isLoading: false});
}
};
}

Resources