Context hook interfares with React Navigation routing - reactjs

My React Native (0.61.5)/React 16.9.0 app has nested 3 layer react navigation (5.x) routing structure like below:
App.js (NavigationContainter/StackNavigator)
SplashScreen.js
AppScreen.js (TabNavigator)
EventStackScreen.js (StackNavigator)
Event.js (screen)
Chat.js (screen)
Newevent.js (screen)
Editevent.js (screen)
GroupStackScreen.js (StackNavigator)
Group.js (screen)
Newgroup.js (screen)
ContactStackScreen.js (StackNavigator)
Contact.js (screen)
Newcontact.js (screen)
*The file extension indicates that it is a single file.
The app will display 3 tabs and route to Chat screen under Event tab until context hook is added to AppScreen.js. With context added, I can see the Event tab and won't be able to open Chat screen.
Here is the code in AppScreen.js:
export default function AppScreen ({route, navigation}) {
console.log("Routes available in AppScreen : ", navigation.dangerouslyGetState().routes)
const authContext = React.createContext(null);
const stateContext = React.createContext(null);
const propsContext = React.createContext(null);
console.log("route in AppScreen : ", route.params);
const {data} = route.params;
//do something here
let authVal = {
myself,
result,
updateToken,
}
let stateVal = {
group_id,
grp_name,
role,
alias,
updateGroup,
updateGrpmember,
}
let propsVal = {
device_id,
}
return (
<propsContext.Provider value={propsVal}>
<authContext.Provider value={authVal}>
<stateContext.Provider value={stateVal}>
<BTab.Navigator>
<BTab.Screen name="Event" component={EventStackScreen} />
<BTab.Screen name="Group" component={GroupStackScreen} />
<BTab.Screen name="Contact" component={ContactStackScreen} />
</BTab.Navigator>
</stateContext.Provider>
</authContext.Provider>
</propsContext.Provider>
);
};
As soon as the 3 context providers are removed, then routing to Chat screen work again. Here is the EventStackScreen.js:
const EStack = createStackNavigator();
export default function EventStackScreen({route, navigation}) {
const data = route.params.data;
//do something....
return (
<EStack.Navigator initialRouteName="Event">
<EStack.Screen name="Event" component={Event} />
<EStack.Screen name="New Event" component={NewEvent} />
<EStack.Screen name="Edit Event" component={EditEvent} />
<EStack.Screen name="Chat" component={Chat} />
</EStack.Navigator>
);
}
I have been rubbing my head quite some time for this routing problem and haven't figured out where is the problem. The last resort is to get rid of the context and pass the variable value in a traditional way even though it is recommended by React Navigation to use context instead.

To solve the problem, move all the context related code into a GlobalContext.js and import it in AppScreen.js with import {authContext, stateContext, propsContext} from "./GlobalContext". Here is the GlobalContext.js:
import React, {Component} from 'react';
const authContext = React.createContext(null);
const propsContext = React.createContext(null);
const stateContext = React.createContext(null);
export {authContext, stateContext, propsContext}

Related

why does a Context reload when user leaves browser tab and returns back

I am using a Context to serve data across all the components of my app. I am using Next.js as the React framework. I notice that every time, the user moves to another tab of the browser and returns to the application tab, the context gets reloaded. I put a log in the useEffect(()=> {console.log(...)}, []) of the Context.js
The effect of this is: In my use-case, the context is to share some db data and functions. Consequently, the database re-loading unnecessarily occurs.
The app is a standard, simple one. I am leaving out the business logic since it is not important to the question. The relevant code is shown below
// _app.js (I am using next.js)
import Layout from 'components/Layout'
import { ThemeProvider } from "next-themes";
import { DataProvider } from 'contexts/DataContext'
...
function MyApp({ Component, pageProps }) {
const router = useRouter()
// simplified the return
return (
<Layout>
<ThemeProvider>
<DataProvider>
<Component {...pageProps} />
</DataProvider>
</ThemeProvider>
</Layout>
)
}
export default MyApp
and the context
# DataContext.js
import { createContext, useEffect, useState, useRef } from "react"
const DataContext = createContext()
export function DataProvider({ children}) {
// some data
const [list, setList] = useState([])
const [somedata, setSomeData] = useState(null)
// some functions
const loadInit = () => {
...
}
const getData = (params) => {
...
}
// other functions
...
...
useEffect(() => {
// this gets called everytime user returns to tab in browser
// causing the loadinit to be reloaded everytime the user
// returns to the open application in the tab
loadinit()
}
}, [])
return (
<DataContext.Provider value={{
list,
somedata,
getData,
....
}}>
{children}
</DataContext.Provider>
)
}
export default DataContext
And this is how the Context is used in any page
// index.js
import DataContext from 'contexts/DataContext'
import Header from 'components/Header'
...
const Home = () => {
...
const { list, getData } = useContext(DataContext)
...
return (
.... list will be used here
)
}
export default Home
Is there a way to avoid it? Currently, I am setting a state in DataContext to check whether it is a first time load or not.
Thanks.

React Native: Rendered fewer hooks than expected

I'm currently trying to develop an app with multiple screens. Specifically, I'm working on the navigator component that directs the user to the login screen or the home screen based on whether they are logged in or not.
To do this, I'm making use of hooks, React Navigation and Firebase. I have a state which tracks the user, and this state is updated using onAuthStateChanged() from Firebase, which is inside a useEffect hook.
import { useState, useEffect } from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import {
HomeScreen,
LoginScreen,
TimerScreen
} from '../screens';
import { auth } from '../firebase';
import { onAuthStateChanged } from 'firebase/auth';
const MainStack = createNativeStackNavigator();
const AppNavigator = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const subscriber = onAuthStateChanged(auth, authUser => {
if (authUser) {
setUser(authUser);
} else {
setUser(null);
}
});
return subscriber;
});
const MainNavigator = () => (
...
);
return (
<NavigationContainer>
{ user ? MainNavigator() : LoginScreen() }
</NavigationContainer>
);
};
export default AppNavigator;
AppNavigator is then called in my App.js:
export default function App() {
return (
<View style={styles.container}>
<StatusBar style="auto" />
<AppNavigator />
</View>
);
}
However, whenever I run the app, I get
Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
I've read a few posts with the same error message, and a common recommendation is to avoid having hooks inside conditional statements / loops. I did check that my useState and useEffect were at the top level of my component, so that doesn't seem to be the issue.
Right now I'm thinking that the problem could be arising because I'm navigating between screens, but I'll have to look more into it though.
Does anyone know what might be the issue, or any other possible fixes I could try? Any help would be great. Thanks!
user ? MainNavigator() : LoginScreen()
You are calling components as regular functions instead of creating elements from them. To create an element, use the JSX syntax, i.e.:
user ? <MainNavigator /> : <LoginScreen />
(Which will then be transpiled to React.createElement.)
The error occurs because when calling these components as functions, the code inside becomes a part of the render phase of the AppNavigator component. If, for example, MainNavigator contains hooks, and LoginScreen does not, then toggling between which function is (incorrectly) called also changes the number of hooks rendered, as suggested in the error message.

React Hooks useContext value not updating

I'm updating a username based on a form input from another component. I put a console.log inside the provider component to make sure it's getting updated... it is! But the value never updates on the component receiving this value.
Here is the provider component:
import React, { useState, useContext } from 'react';
export const GetFirstName = React.createContext();
export const GetLastName = React.createContext();
export const SetUserName = React.createContext();
export const UserNameProvider = ({ children }) => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
console.log(firstName);
return (
<SetUserName.Provider value={{ setFirstName, setLastName }}>
<GetFirstName.Provider value={firstName}>
<GetLastName.Provider value={lastName}>
{children}
</GetLastName.Provider>
</GetFirstName.Provider>
</SetUserName.Provider>
);
};
Account page (wraps the component with the provider so it can receive context):
import React, { useContext } from 'react';
import { useHistory } from 'react-router-dom';
import { GetLoggedIn, UserNameProvider } from '../Providers/providers.js';
import AccountHeader from './Account/AccountHeader.js';
import AccountItemsList from './Account/AccountItemsList.js';
import LoginModal from './Modal/LoginModal.js';
const Account = () => {
const history = useHistory();
const loggedIn = useContext(GetLoggedIn);
return !loggedIn ? (
<LoginModal closeModal={history.goBack} />
) : (
<div id='account-div'>
<UserNameProvider>
<AccountHeader />
</UserNameProvider>
<AccountItemsList /> // within AccountItemsList,
// another component is wrapped the same way
// to use setFirstName and setLastName
// this works fine, as the console.log shows
</div>
);
};
export default Account;
And finally the AccountHeader page, which receives only the initial value of '', then never reflects the current value after another component calls setFirstName.
import React, { useContext } from 'react';
import { GetFirstName } from '../../Providers/providers.js';
const AccountHeader = () => {
const firstName = useContext(GetFirstName);
return (
<div id='account-top'>
<img src='#' alt='User' />
<h1>{firstName}</h1>
</div>
);
};
Just to check my sanity I implemented a really simple version of this in a codepen and it works as it should. Elsewhere in my app I'm using context to check if the user is logged in. That is also working as it should. I've pulled almost all the hair out of my head.

React Native: where to place global state variables

I am developing my first React Native, and I need again some help.
My application has one state - sport, which will be important for components, screens and etc. Accordingly the chosen sport, I will load different styles, images, and api information too.
There will be one modal, from which the user can change the sport. The modal now is part of the Header component, which is part of the Screen component.
So my question is how or where to place this sport state variable, so I can access it everywhere and on a change to update the new styles and etc.
The overview of the application is like this:
App.js
import AppContext from './utility/context';
export default function App() {
const [sport, setSport] = React.useState('soccer');
const state = {sport, setSport};
return (
<AppContext.Provider sport={state}>
<OfflineNotice />
<Screen />
</AppContext.Provider>
);
}
context.js
import React from "react";
export const AppContext = React.createContext({
sport: 'soccer',
setSport: () =>{}
});
Screen.js
export default function Screen ({children}) {
return (
<>
<Header />
<SafeAreaView style={styles.screen}>
<View style={styles.container}>{ children }</View>
</SafeAreaView>
<TabNavigator i18n={i18n}/>
</>
);
}
In Header.js I will also use that future state, but at the moment there is nothing interesting.
But here will be the View, from which the user will change the sport state variable.
HomeScreen.js - it is the first screen of the TabNavigator
export default function HomeScreen({ navigation }) {
const today = Moment(new Date().getTime()).format('YYYY-MM-DD');
const [predictions, setPredictions] = useState([]);
const [loading, setLoading] = useState(true);
const params = {
lang: 'en',
date: today,
sport: 'soccer',
};
...
}
Here the sport state is hardcoded because I don't know yet how to proceed.
I've heard about Redux, but I haven't used it yet, so I will appreciate if there is any solution not using Redux.
Thank you in advance!
You can achieve this using React-Context
You can simply have a state in the app.js and use context to access it anywhere you need in the app.
First you need to create the context. Its better if its a separate file
const AppContext = React.createContext({sport:'value',setSport=()=>{}});
Here the default values are options but preferred specially when you use typescript to avoid warnings.
Now you have to use this in your app.js like below
export default function App() {
const [sport,setSport] = React.useState('value');
const state={sport,setSport};
...
return (
<AppContext.Provider value={state}>
<OfflineNotice />
<Screen />
</AppContext.Provider>
);
}
You will have to import the context and use the provider as the wrapper setting the value from the local state that you have. Now you can access this anywhere in the tree and modify it if required.
// Accessing the context using the useContext hook, this component should be in the tree and you should import AppContext
const {sport,setSport} = useContext(AppContext);
You can show it like below
<Text>{sport}</Text>
Or set it like below
<Button title="Set Value" onPress={()=>{setSport('value')}}>
This example is just on a string but you can even have an object.

Next.js app doesn't seem to preserve global context provider set up within _app.js

I have my next.js app set up with a global context/provider dealy:
_app.js:
import {useState} from "react"
import GlobalContext from "components/GlobalContext"
function MyApp({ Component, pageProps }) {
const [isLoggedIn, setIsLoggedIn] = useState(null)
let contextValues = {
isLoggedIn, setIsLoggedIn
}
return (
<GlobalContext.Provider value={contextValues}>
<Component {...pageProps} />
</GlobalContext.Provider>
)
}
export default MyApp
components/GlobalContext.js:
const { createContext } = require("react")
const GlobalContext = createContext()
export default GlobalContext
When I call setIsLoggedIn(true) elsewhere in the app... it appears to be set just fine, but then when I navigate to a different page, it seems this global state gets blasted away and isLoggedIn is set back to false.
Is this how it's supposed to work?
How should I manage global state such as this, and also have it persist across page loads?
Thanks.
As I understand it, every time you go to a different page, the function MyApp is run, so a new 'instance' of the component's state is created.
To reuse the state, it sould be stored in a global state. I did that using an external redux store, and calling it from myapp. This store is a global variable and it's reused if it exists. Kind of the 'singleton' pattern.
let store: Store<StoreState, Action>;
const initStore = () => {
if (!store) {
store = createStore(reducer, initialState);
}
return store;
};
export const useStore = () => initStore();
Them from MyApp
import { useStore } from '../store';
import { Provider } from 'react-redux';
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
const store = useStore();
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
}
You can maybe do the same without redux. The key is to keep the state as global variable.
However, this will work only from client side navigation. If you retrieve a page using server side, you'll lose the state, you'll need to use sessions or cookies for that.
You can also check this example from nextjs:
https://github.com/vercel/next.js/tree/canary/examples/with-redux

Resources