react native memory leak react navigation - react-native

I want to check If the user has a secure Token in a useEffect but I get this error Message.
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.
This happens when I use the useEffect. If I remove it, then I get no error message but I need to check if the user has the token.
import React, { useEffect } from 'react';
import { View, Text } from 'react-native';
import getSecureKey from '../utilies/getSecureKey';
const Stack = createStackNavigator();
const AppStack = ({ navigation }) => {
useEffect(() => {
getSecureKey().then(res => console.log(res)).catch(e => console.log(e));
}, []);
return (
<Stack.Navigator showIcon={true} initialRouteName="AppTabs">
<Stack.Screen name="AppTabs" component={AppTabs} options={{headerTitle: () => <Header />, headerStyle: {
backgroundColor: '#fff'
}}} />
.....
getSecureToken:
import * as SecureStore from 'expo-secure-store';
const getSecureKey = async () => {
const key = await SecureStore.getItemAsync('jwt');
return key;
};
export default getSecureKey;
App.js
import React, { useState, useEffect } from 'react';
import * as Font from 'expo-font';
import { NavigationContainer } from '#react-navigation/native';
import AppLoading from 'expo-app-loading';
import { Provider } from 'react-redux';
import store from './src/redux/store/index';
import AppStack from './src/navigation/stack';
const getFonts = async () => {
await Font.loadAsync({
"nunito-regular": require("./assets/fonts/Nunito-Regular.ttf"),
"nunito-bold": require("./assets/fonts/Nunito-Bold.ttf"),
});
};
const App = () => {
const [fontsLoaded, setFontsLoaded] = useState(false);
if(fontsLoaded) {
return (
<Provider store={store}>
<NavigationContainer><AppStack /></NavigationContainer>
</Provider>)
} else {
return (<AppLoading startAsync={getFonts} onFinish={() => setFontsLoaded(true)} onError={() => {}} />)
}
};
export default App;

Don't restore token in the navigator. Instead do this -
Firstly, install expo-app-loading from here
Then, create a folder called navigation where your App.js is located. Then inside it create a File called AppNavigator.js.
Inside AppNavigator.js, paste this
import React, { useEffect } from 'react';
import { View, Text } from 'react-native';
import { createStackNavigator } from '#react-navigation/stack';
import getSecureKey from '../utilities/getSecureKey';
const Stack = createStackNavigator();
const AppNavigator = () => {
// Remove these Lines --
// useEffect(() => {
// getSecureKey()
// .then((res) => console.log(res))
// .catch((e) => console.log(e));
// }, []);
return (
<Stack.Navigator showIcon={true} initialRouteName="AppTabs">
<Stack.Screen
name="AppTabs"
component={AppTabs}
options={{
headerTitle: () => <Header />,
headerStyle: {
backgroundColor: '#fff',
},
}}
/>
</Stack.Navigator>
);
};
export default AppNavigator;
For your fonts create a folder called hooks where your App.js is located and inside that create a file useFonts.js
In useFonts.js write like this -
import * as Font from "expo-font";
export default useFonts = async () => {
await Font.loadAsync({
"nunito-regular": require("./assets/fonts/Nunito-Regular.ttf"),
"nunito-bold": require("./assets/fonts/Nunito-Bold.ttf"),
});
};
In your App.js
import React, { useState } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import { NavigationContainer } from '#react-navigation/native';
import AppLoading from 'expo-app-loading';
import useFonts from "./hooks/useFonts";
import getSecureKey from './utilities/getSecureKey';
import AppNavigator from './navigation/AppNavigator';
export default function App() {
const [IsReady, SetIsReady] = useState(false);
// Always perform Token Restoration in App.js just to keep code clear.
const FontAndTokenRestoration = async () => {
await useFonts(); // Font is being loaded here
const token = await getSecureKey();
if (token) {
console.log(token);
}
};
if (!IsReady) {
return (
<AppLoading
startAsync={FontAndTokenRestoration}
onFinish={() => SetIsReady(true)}
onError={() => {}}
/>
);
}
return (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);
}

Related

react-native Unable to retrieve params via deep link

unable to pass params from deep link. getting undefined when running:
npx uri-scheme open [prefix]://news/3 --android
NewsScreen.js
import React from 'react';
const NewsScreen = ({ route, navigation }) => {
console.log(route.params); // undefined
};
Linking.js
import LINKING_PREFIXES from 'src/shared/constants';
export const linking = {
prefixes: LINKING_PREFIXES,
config: {
screens: {
Home: {
screens: {
News: {
path: 'news/:id?',
parse: {
id: id => `${id}`,
},
},
},
},
NotFound: '*',
},
},
};
Router.js
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import {useAuth} from 'src/contexts/AuthContext';
import {Loading} from 'src/components/Loading';
import {AppStack} from './AppStack';
import {AuthStack} from './AuthStack';
import {GuestStack} from './GuestStack';
import SplashScreen from 'src/screens/guest/SplashScreen';
import linking from './Linking.js';
export const Router = () => {
const {authData, loading, isFirstTime, appBooting} = useAuth();
if (loading) {
return <Loading />;
}
const loadRoutes = () => {
if (appBooting) {
return <SplashScreen />;
}
if (isFirstTime) {
return <GuestStack />;
}
if (!authData || !authData.name || !authData.confirmed) {
return <AuthStack />;
}
return <AppStack />;
};
return <NavigationContainer>{loadRoutes()}</NavigationContainer>;
};
AppStack.js
import React from 'react';
import {createDrawerNavigator} from '#react-navigation/drawer';
import {SpeedNewsStack} from 'src/routes/NewsStack';
const Drawer = createDrawerNavigator();
export const AppStack = () => {
return (
<Drawer.Navigator>
<Drawer.Screen name="Home" component={NewsStack} />
</Drawer.Navigator>
);
};
NewsStack.js
import React from 'react';
import {createStackNavigator} from '#react-navigation/stack';
import SpeedNewsScreen from 'src/screens/NewsScreen';
const Stack = createStackNavigator();
export const NewsStack = () => {
return (
<Stack.Navigator>
<Stack.Screen name="News" component={NewsScreen} />
</Stack.Navigator>
);
};

How to use redux component in App.js React Native?

I'm doing a simple counter app. It has one label, and a button that you can increment by + 1 (each time it's pushed).
Using redux, I want to use the count that I store (in my Redux Store) in App.js file. However, I'm getting an error:
Error: could not find react-redux context value; please ensure the component is wrapped in a Provider
Using the useSelector works in other files, just not App.js. Is there a work around?
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Dogs from './components/Dogs';
import { Provider, useSelector } from 'react-redux';
import store from './redux/configureStore'
export default function App() {
const count = useSelector((state) => state.counter.count);
{/*useSelector does not work in this file!*/}
return (
<Provider store={store}>
<View style={styles.container}>
<Text>{`ha ${count}`}</Text>
<Dogs />
</View>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Counter.js
import React, { useState, useEffect } from "react";
import { View, Text, StyleSheet, Button} from "react-native";
import { useDispatch, useSelector } from "react-redux";
import { increment } from '../redux/ducks/counter'
const Counter = () => {
const count = useSelector((state) => state.counter.count);
{/*useSelector works in this file!*/}
const dispatch = useDispatch();
const handleIncrement = () => {
dispatch(increment())
};
return (
<div>
{/* <Text>{` COunt: ${count}`}</Text> */}
<Button onPress={handleIncrement}>Increment</Button>
</div>
);
}
const styles = StyleSheet.create({})
export default Counter;
redux/configureStore.js
import { combineReducers, createStore } from 'redux';
import counterReducer from './ducks/counter';
const reducer = combineReducers({
counter: counterReducer
});
const store = createStore(reducer);
export default store;
redux/ducks/counter.js
const INCREMENT = 'increment';
export const increment = () => ({
type: INCREMENT
})
const initialState = {
count: 0
};
export default ( state = initialState, action) => {
switch(action.type) {
case INCREMENT:
return{...state, count: state.count + 1}
default:
return state;
}
};
As error saying, you are using useSelector out side of provider. In your app.js you are using useSelector before the app renders, so it is not able to find store. So, create a component for functionality which you want to use in app.js like this :
Create a file, call it anything like CountView.js, in CountView.js use your redux login :
CountView.js
import React from 'react';
import { Text } from 'react-native';
import { useSelector } from 'react-redux';
const CountView = () => {
const count = useSelector((state) => state.counter.count);
return (
<Text>{`ha ${count}`}</Text>
)
}
export default CountView;
Now, In your app.js use this component :
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Dogs from './components/Dogs';
import { Provider } from 'react-redux';
import store from './redux/configureStore'
import CountView from '../components/CountView'; // import CountView component
export default function App() {
return (
<Provider store={store}>
<View style={styles.container}>
{/* Use component here */}
<CountView />
<Dogs />
</View>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Keep other things as it is, and now your functionality will works.
useSelector will work only if you wrap it inside Provider. you can create a wrapper file for App.
const AppWrapper = () => {
return (
<Provider store={store}> // Set context
<App /> // Now App has access to context
</Provider>
)
}
In App.js
const App = () => {
const count = useSelector((state) => state.counter.count); // will Work!
}
Unlike a regular React application, an expo React-Native application is not wrapped using an index.js file. Therefore when we wrap the provider in app.js for a React-Native app, we wrap it in index.js for React application. So the hooks like useSelector or useDispatch run before the provider is initialized. So, I would suggest not using any hooks in the app component, instead, we can create other components in the app.js and use the hooks in a separate component like in the code I have used below.
const Root = () => {
const [appIsReady, setAppIsReady] = useState(false);
const dispatch = useDispatch();
const fetchToken = async () => {
const token = await AsyncStorage.getItem("token");
console.log("Stored Token: ", token);
if (token) {
dispatch(setAuthLogin({ isAuthenticated: true, token }));
}
};
const LoadFonts = async () => {
await useFonts();
};
useEffect(() => {
async function prepare() {
try {
await SplashScreen.preventAutoHideAsync();
await LoadFonts();
await fetchToken();
} catch (e) {
console.warn(e);
} finally {
setAppIsReady(true);
}
}
prepare();
}, []);
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
return (
<NavigationContainer onReady={onLayoutRootView}>
<MainNavigation />
</NavigationContainer>
);
};
export default function App() {
return (
<>
<Provider store={store}>
<ExpoStatusBar style="auto" />
<Root />
</Provider>
</>
);
}

Fonts loading, but I need to use Font.loadAsync

I'm trying to add a custom font to ReactNative using expo
the fonts are actually showing up ok for now, but I keep getting a warning that I should use Font.loadAsync, I've tried to adapt what's in the docs to the tabbed template and watched a few tutorials, most recently Maximillian's on Udemy feels the closest.. but even though I'm using Font.loadAsync I'm still getting the same warning.
Repo, Tabs Template with Custom Fonts
import { StatusBar } from 'expo-status-bar';
import React, { useState } from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import useCachedResources from './hooks/useCachedResources';
import useColorScheme from './hooks/useColorScheme';
import Navigation from './navigation';
// expo install expo-app-loading
import AppLoading from 'expo-app-loading';
// import { AppLoading } from 'expo';
// import { useFonts } from 'expo-font';
import * as Font from 'expo-font';
const fetchFonts = () => {
return Font.loadAsync({
'CharterBold': require('./assets/fonts/CharterBold.otf'),
'CharterBoldItalic': require('./assets/fonts/CharterBoldItalic.otf'),
'CharterItalic': require('./assets/fonts/CharterItalic.otf'),
'CharterRegular': require('./assets/fonts/CharterRegular.otf'),
'SpaceMono': require('./assets/fonts/SpaceMono-Regular.ttf'),
})
}
export default function App() {
const isLoadingComplete = useCachedResources();
const colorScheme = useColorScheme();
const [fontsLoaded, setFontsLoaded] = useState(false)
// const [loaded] = useFonts({
// CharterBold: require('./assets/fonts/CharterBold.otf'),
// CharterBoldItalic: require('./assets/fonts/CharterBoldItalic.otf'),
// CharterItalic: require('./assets/fonts/CharterItalic.otf'),
// CharterRegular: require('./assets/fonts/CharterRegular.otf'),
// SpaceMono: require('./assets/fonts/SpaceMono-Regular.ttf'),
// });
if (!fontsLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => setFontsLoaded(true)}
onError={(err) => console.log(err)}
/>
);
}
if (!isLoadingComplete) {
return null;
} else {
return (
<SafeAreaProvider>
<Navigation colorScheme={colorScheme} />
<StatusBar />
</SafeAreaProvider>
);
}
}
Create a folder called hooks where your App.js is located.
Inside that create a file called useFonts.js
Your useFonts.js should look like this -
import * as Font from "expo-font";
const useFonts = async () => {
await Font.loadAsync({
CharterBold: require("./assets/fonts/CharterBold.otf"),
CharterBoldItalic: require("./assets/fonts/CharterBoldItalic.otf"),
CharterItalic: require("./assets/fonts/CharterItalic.otf"),
CharterRegular: require("./assets/fonts/CharterRegular.otf"),
SpaceMono: require("./assets/fonts/SpaceMono-Regular.ttf"),
});
}
export default useFonts;
Now your App.js should look like this -
import { StatusBar } from "expo-status-bar";
import React, { useState } from "react";
import { SafeAreaProvider } from "react-native-safe-area-context";
import useCachedResources from "./hooks/useCachedResources";
import useColorScheme from "./hooks/useColorScheme";
import Navigation from "./navigation";
import AppLoading from "expo-app-loading";
import useFonts from "./hooks/useFonts";
export default function App() {
const isLoadingComplete = useCachedResources();
const colorScheme = useColorScheme();
const [IsReady, SetIsReady] = useState(false);
const FetchFonts = async () => {
await useFonts();
};
if (!IsReady) {
return (
<AppLoading
startAsync={FetchFonts}
onFinish={() => SetIsReady(true)}
onError={(err) => console.log(err)}
/>
);
}
return (
<SafeAreaProvider>
<Navigation colorScheme={colorScheme} />
<StatusBar />
</SafeAreaProvider>
);
}
I found what I need under ./hooks/useCachedResources.ts
there is already a section that is using await Font.loadAsync
I just needed to add my fonts to that section.

React Native useContext hook returns Undefined

I am new to react native and context Api so any help would be really appreciated. When I start the app I see undefined is not an object _useContext.appUser error. Below is my code.
App.js
import { AsyncStorage } from 'react-native';
import { NavigationContainer } from '#react-navigation/native'
import AuthStackNavigator from './src/navigators/AuthStackNavigator'
import { LightTheme } from './src/themes/light'
import UserTabsNavigator from './src/navigators/UserTabsNavigator'
import AuthProvider from './src/auth/AuthProvider'
import { AuthContext } from './src/auth/AuthProvider';
export default function App() {
const [loggedIn, setLoggedIn] = useState(false);
const { appUser } = useContext(AuthContext);
console.log('context object' + appUser);
useEffect(() => {
AsyncStorage.getItem('user').then(userString => {
if (userString) {
setLoggedIn(true)
}
}).catch(error => {
console.log(error);
})
})
return (
<AuthProvider>
<NavigationContainer theme={LightTheme}>
{loggedIn ? <UserTabsNavigator /> :
<AuthStackNavigator />}
</NavigationContainer>
</AuthProvider>
);
};
AuthProvider.js
import React, { useState, createContext } from 'react';
import { AsyncStorage } from 'react-native';
export const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const loginUser = () => {
const fakeUser = { username: 'Test' }
AsyncStorage.setItem('user', JSON.stringify(fakeUser));
setUser(fakeUser);
}
const logoutUser = () => {
AsyncStorage.removeItem('user');
setUser(null);
}
return (
<AuthContext.Provider value={{
appUser: user,
login: loginUser,
logout: logoutUser
}}>
{children}
</AuthContext.Provider>
)
}
export default AuthProvider;
I would really appreciate any help here. I have been struggling with this issue for a while now. I am kinda stuck here.

Can't connect components with mapDispatchToProps

I have React Native project with Redux and I'm trying to connect the actions to the components.
I have App.js file without index.js file.
This is how I implement Redux:
App.js:
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { Provider } from 'react-redux';
import store from './src/store/Store.js';
import AppNavigator from './src/navigation/AppNavigator';
export default function App(props) {
return (
<Provider store = { store }>
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AppNavigator />
</View>
</Provider>
);
}
AppNavigator.js:
import React from 'react';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import MainTabNavigator from './MainTabNavigator';
export default createAppContainer(
createSwitchNavigator({
Main: MainTabNavigator
})
);
MainTabNavigator.js: (Only the relevant part)
import React from 'react'
import {connect} from 'react-redux';
import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';
import {HomeScreen} from '../screens/HomeScreen';
import * as CounterActions from '../store/actions/CounterActions';
let HomePage = connect(state => mapStateToProps)(HomeScreen);
const HomeStack = createStackNavigator(
{
Home: HomePage,
},
config
);
const tabNavigator = createBottomTabNavigator({
HomeStack,
SettingsStack,
});
const mapStateToProps = (state) => {
return {
count: state.counter.count
}
};
const mapDispatchToProps = {
...CounterActions
};
export default tabNavigator;
CounterActions.js:
export const increment = (number) => {
return (dispatch) => {
dispatch({ type: 'INCREMENT', number })
}
};
export const decrement = (number) => {
return (dispatch) => {
dispatch({ type: 'DECREMENT', number })
}
};
The following line in MainTabNavigator.js connects the state to props of the HomeScreen component:
let HomePage = connect(state => mapStateToProps)(HomeScreen);
HomeScreen.js:
import React from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';
export const HomeScreen = (props) => {
alert(JSON.stringify(props));
return (
<View style={styles.container}>
<Text>COUNT FROM STORE: {props.count}</Text>
</View>
);
};
HomeScreen components gets the state correctly and render 'count', but How do I connect the actions?
I want HomeScreen to dispatch like this:
props.increment(1);
Thanks!
The mapDispatchToProps is the second argument of the connect function from the react-redux.
I also think that you pass wrong the first argument to the connect.
Try this:
let HomePage = connect(mapStateToProps, mapDispatchToProps)(HomeScreen);
Finally solved it by the following way:
MainTabNavigator.js:
import React from 'react'
import {connect} from 'react-redux';
import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';
import {HomeScreen} from '../screens/HomeScreen';
import {increment, decrement} from '../store/actions/CounterActions';
let HomePage = connect(state => mapStateToProps, dispatch => mapDispatchToProps(dispatch))(HomeScreen);
const HomeStack = createStackNavigator(
{
Home: HomePage,
},
config
);
const tabNavigator = createBottomTabNavigator({
HomeStack,
SettingsStack,
});
const mapStateToProps = (state) => {
return {
count: state.counter.count
}
};
const mapDispatchToProps = (dispatch) => {
return {
increment: (number) => dispatch(increment(number)),
decrement: (number) => dispatch(decrement(number))
}
};
export default tabNavigator;
Follow along, we will make some modifications to your files:
First lets modify your MainTabNavigator.js since you only posted the relevant part, make sure to implement this for the rest as well.
import React from 'react'
import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';
import {HomeScreen} from '../screens/HomeScreen';
let HomePage = connect(state => mapStateToProps)(HomeScreen); // <===== Remove this
const HomeStack = createStackNavigator(
{
Home: HomePage, // <===== Make this HomeScreen instead of HomePage
},
config
);
const tabNavigator = createBottomTabNavigator({
HomeStack,
SettingsStack,
});
const mapStateToProps = (state) => {
return {
count: state.counter.count
}
};
const mapDispatchToProps = {
...CounterActions
};
export default tabNavigator;
What we want is to have the mapping of state and props on the Home Screen itself (or any other screen)
Now lets move on to your HomeScreen.js:
import React from 'react';
import { connect } from 'react-redux';
import { Platform, StyleSheet, Text, View } from 'react-native';
import { increment, decrement } from '../store/actions/CounterActions'; // <===== import your actions here, preferably like this
/** add the following: */
const mapStateToProps = (state, ownProps) => ({
// ... computed data from state and optionally ownProps
});
const mapDispatchToProps = {
// ... normally is an object full of action creators
increment, // <===== Map your dispatch here to props
decrement // <===== Mapping the second dispatch
};
const HomeScreen = (props) => {
alert(JSON.stringify(props));
return (
<View style={styles.container}>
<Text>COUNT FROM STORE: {props.count}</Text>
</View>
);
};
/** Export your component like this */
export default connect(
mapStateToProps,
mapDispatchToProps
)(HomeScreen)
Now anywhere on your HomeScreen.js you can call this.props.increment(yourNumber) or this.props.decrement(yourNumber) and you should be good to go
Hope this Helps!