Why am I getting undefine? - reactjs

I want to make setLoading function from "Screen" to be available in "HomeScreen". So
context.js
import { createContext } from "react";
export const LoadingContext = createContext();
screen.js
This is screen component (parent component for HomeScreen). And I want to control the state of Modal in HomeScreen (child component).
import { Modal, StyleSheet, View } from "react-native";
import { useMemo, useState } from "react";
import { LoadingContext } from "../../context";
const Screen = ({ children}) => {
const [loading, setLoading] = useState(false);
const loadingContext = useMemo(() => {
return { setLoading: setLoading };
}, [loading]);
return (
<LoadingContext.Provider value={loadingContext}>
<View>
{children}
</View>
</LoadingContext.Provider>
);
};
export default Screen;
HomeScreen.js
import { View, Text } from "react-native";
import { LoadingContext } from "../../context";
import { useContext} from "react";
const HomeScreen = ({ navigation }) => {
const context = useContext(LoadingContext);
console.log(context); // undefined
return (
<Screen>
<Text></Text>
</Screen>
);
};
export default HomeScreen;

The reason is your useContext isn't inside your Screen component. Read the docs,
The current context value is determined by the value prop of the nearest <MyContext.Provider> above the calling component in the tree.
Your useContext looks up the contexts in parent nodes but fails and hence the undefined value.
Try rapping the HomeScreen with Screen component when you are using it which I am assuming is your router.
EDIT:
Here's your fix
import { View, Text } from "react-native";
import { LoadingContext } from "../../context";
import { useContext} from "react";
const HomeScreen = ({ navigation }) => {
const context = useContext(LoadingContext);
console.log(context); // undefined
return <Text></Text>;
};
const WrappedHomeScreen = (props) => {
return <Screen><HomeScreen {...props} /></Screen>;
}
export default WrappedHomeScreen;

Related

MobX changing but not rerendering components

I would like to open and close an antd Modal component using a MobX store. I have the following code Here's a link to codesandbox https://codesandbox.io/s/nifty-dijkstra-g0kzs6?file=/src/App.js
import AppNavigation from "./components/Menu";
import ContactPopUp from "./components/Contact";
export default function App() {
return (
<div className="App">
<AppNavigation />
<ContactPopUp />
</div>
);
}
File with the MobXstore
import { createContext, useContext } from "react";
import AppStore from "./appStore";
interface Store {
appStore: AppStore;
}
export const store: Store = {
appStore: new AppStore()
};
export const StoreContext = createContext(store);
export function useStore() {
return useContext(StoreContext);
}
Separate file where I declare the store
import { makeAutoObservable } from "mobx";
export default class AppStore {
contactFormOpen = false;
constructor() {
makeAutoObservable(this);
}
setContactFormOpen = (isOpen: boolean) => {
console.log("Changed contact form to ", isOpen);
this.contactFormOpen = isOpen;
};
}
The Menu.tsx
import React from "react";
import { Menu, MenuProps } from "antd";
import { useStore } from "../store/store";
const AppNavigation = () => {
const { appStore } = useStore();
const menuItems: MenuProps["items"] = [
{
label: <a onClick={(e) => handleOpenContactForm(e)}>Contact</a>,
key: "Contact"
}
];
const handleOpenContactForm = (e: any) => {
e.preventDefault();
e.stopPropagation();
appStore.setContactFormOpen(true);
console.log("Open contact pop up", appStore.contactFormOpen);
};
return (
<Menu
items={menuItems}
theme="dark"
overflowedIndicator={""}
className="header__menu award-menu header__menu--md"
/>
);
};
export default AppNavigation;
ContactPopUp.tsx
import { Modal } from "antd";
import React, { useEffect, useState } from "react";
import { useStore } from "../store/store";
const ContactPopUp = () => {
const { appStore } = useStore();
const [visible, setVisible] = useState(appStore.contactFormOpen);
useEffect(() => {
setVisible(appStore.contactFormOpen);
}, [appStore.contactFormOpen]);
const handleCancel = () => {
appStore.setContactFormOpen(false);
console.log("Close contact from", appStore.contactFormOpen);
};
return (
<Modal title="Contact us" visible={visible} onCancel={handleCancel}>
<h2>Modal Open</h2>
</Modal>
);
};
export default ContactPopUp;
The mobx contactFormOpen clearly changes but the modal state does not. I really don't understand why... UseEffect also doesn't trigger a re render.
You just forgot most crucial part - every component that uses any observable value needs to be wrapped with observer decorator! Like that:
const ContactPopUp = () => {
const { appStore } = useStore();
const handleCancel = () => {
appStore.setContactFormOpen(false);
console.log('Close contact from', appStore.contactFormOpen);
};
return (
<Modal
title="Contact us"
visible={appStore.contactFormOpen}
onCancel={handleCancel}
>
<h2>Modal Open</h2>
</Modal>
);
};
// Here I've added `observer` decorator/HOC
export default observer(ContactPopUp);
And you don't need useEffect or anything like that now.
Codesandbox

How to Navigate to Screen inside Action Creators with React Navigation 5

In React Navigation 5 Auth Flow, it says screen will automatically navigate when conditional state changes.
I have setup the screen to navigate to HomeScreen when the state of
isAuthenticated changes to true.
There is no error in the console. The isAuthenticated does change to
true, but the screen is not navigating to HomeScreen after state
change.
I also tried using alternatives like {NavigationActions} and
{CommonActions} in the action creators. to force navigation, but are
also not working.
AuthStackNav.js
import React from 'react';
import { connect } from 'react-redux';
import {createStackNavigator} from '#react-navigation/stack';
import AuthScreen from '../screens/AuthScreen';
import HomeScreen from '../screens/HomeScreen';
const AuthStackNav = ({isAuthenticated}) => {
const AuthStack = createStackNavigator();
return (
<AuthStack.Navigator initialRouteName='Auth'>
{
isAuthenticated ?
<AuthStack.Screen name='Home' component={HomeScreen} />
:
<AuthStack.Screen name='Auth' component={AuthScreen} />
}
</AuthStack.Navigator>
);
};
const mapStateToProps = ({isAuthenticated}) => {
return {isAuthenticated};
};
export default connect(mapStateToProps, null)(AuthStackNav);
userActions.js
import {LOGIN_WITH_FACEBOOK} from './types';
import {NavigationActions} from 'react-navigation';
import { CommonActions } from '#react-navigation/native';
export const loginWithFacebook = () => (dispatch) => {
dispatch({ type: LOGIN_WITH_FACEBOOK, payload: {isAuthenticated: true} });
dispatch(NavigationActions.navigate({routeName:'Home'}));
dispatch( CommonActions.navigate({ name: 'Home' }) );
};
userReducer.js
import {LOGIN_WITH_FACEBOOK} from '../actions/types.js';
const initialState = {
isAuthenticated: false
};
const userReducer = (state=initialState, action) => {
switch(action.type){
case LOGIN_WITH_FACEBOOK:
return {...state, ...action.payload};
default:
return state;
}
}
export default userReducer;
AuthScreen.js
import React from 'react';
import {Text, View, StyleSheet, Image, TouchableOpacity, SafeAreaView} from 'react-native';
import { connect } from 'react-redux';
import {loginWithFacebook} from '../actions/userActions';
const AuthScreen = ({loginWithFacebook}) => {
return (
<View style={styles.screenContainer}>
<TouchableOpacity
activeOpacity={0.5}
onPress={loginWithFacebook}
> Facebook Login</TouchableOpacity>
</View>
);
};
const mapDispatchToProps = {
loginWithFacebook
};
export default connect(null, mapDispatchToProps)(AuthScreen);
AppNav.js
import React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import AuthStackNav from './AuthStackNav';
const AppNav = () => {
return (
<NavigationContainer>
<AuthStackNav />
</NavigationContainer>
);
};
export default AppNav;
I was able to solve it with this guide.
RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
AppNav.js
import { navigationRef } from './RootNavigation.js';
const AppNav = () => {
return (
<NavigationContainer ref={navigationRef} >
<AuthStackNav />
</NavigationContainer>
);
};
export default AppNav;
userActions.js
export const loginWithFacebook = () => (dispatch) => {
dispatch({ type: LOGIN_WITH_FACEBOOK, payload: {isAuthenticated: true} });
RootNavigation.navigate('Home');
};
The answer of Kaizen Tamashi is correct.
In addition, if you want the goBack functionality just add this code inside RootNavigation.js
export function goBack() {
navigationRef.current?.goBack();
}

Consuming a context within another context in React

I currently have 2 contexts within my React app and I was trying to call a method from my top-level context within my 2nd context.
Here is how the context are nested:
App.js
function App(props) {
return (
<SessionContextProvider>
<APIContextProvider>
// I have some components here
</APIContextProvider>
</SessionContextProviders>
)
}
is there a way to consume the SessionContext within my APIContextProvider?
import { SessionContext } from 'contexts/session'
export const APIContext = createContext();
export default class APIContextProvider extends Component {
static contextType = SessionContext
randomMethod() {
this.context.logoutUser()
}
render() {
return (
<APIContext.Provider value={{randomMethod: this.randomMethod}}>
{this.props.children}
</APIContext.Provider>
)
}
}
The issue is that when running randomMethod within my APIContext doesn't work because this.context is undefined.
Is this feasible or am I missing something?
I created an example for you, where ApiProvider uses logoutUser from SessionContext and providing randomMethod, which calls the function logoutUser.
import React, { createContext } from "react";
const SessionContext = createContext();
const SessionProvider = props => {
const logoutUser = () => {
alert("Logout user, but fast!");
};
return (
<SessionContext.Provider value={logoutUser}>
{props.children}
</SessionContext.Provider>
);
};
export { SessionContext as default, SessionProvider };
Inner context
import React, { createContext, useContext } from "react";
import SessionContext from "./SessionContext";
const ApiContext = createContext();
const ApiProvider = props => {
const logoutUser = useContext(SessionContext);
const randomMethod = () => {
logoutUser();
};
return (
<ApiContext.Provider value={{ randomMethod: randomMethod }}>
{props.children}
</ApiContext.Provider>
);
};
export { ApiContext as default, ApiProvider };
App.js
export default function App() {
return (
<SessionProvider>
<ApiProvider>
<TestComponent />
</ApiProvider>
</SessionProvider>
);
}
https://codesandbox.io/s/late-bush-959st

React-native useContext returns undefined

I am trying to learn react and stuck at useContext. I have tried to export Provider and also change value data from provider but still it is returning undefined !
My Context
import React from 'react';
const BlogContext = React.createContext();
export const BlogProvider = ({ children }) => {
const blogPosts = [
{ title: "Blog Post #1" },
{ title: "Blog Post #2" }
];
return (
<BlogContext.Provider value={blogPosts}>
{children}
</BlogContext.Provider>
);
};
export default BlogContext;
My Screen
import React, { useContext } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { BlogContext } from '../context/BlogContext';
const IndexScreen = () => {
const blogPosts = useContext(BlogContext);
console.log(blogPosts);
return (
<View>
<Text>Index Screen</Text>
</View>
);
};
const styles = StyleSheet.create({
});
export default IndexScreen;
When you use export const ... You import it with curly brackets { }
If you used export default ... You should import without the curly brackets.
So you have mixed it:
// just export the context
export const BlogContext = createContext()
const BlogProvider = ({ children }) => {
const blogPosts = [...]
return (
<BlogContext.Provider value={blogPosts}>
{children}
</BlogContext.Provider>
)
}
// export default the component
export default BlogProvider
Second, You need to Wrap your App with your BlogProvider component:
// App.js
import React from "react"
import BlogProvider from "./context/AppContext"
import IndexScreen from "./screens/IndexScreen"
const App = () => {
return (
<BlogProvider>
<IndexScreen />
// More screens...
</BlogProvider>
)
}
export default App
Here is a working codeSandbox example.

Use react navigation.navigate inside a normal function - (no hooks allowed i.e. useNavigation)?

I'm trying to put a function that navigates to a screen in an other module and export it, but navigation does not work. I tried with UseNavigation() but I get an error, namely: Unhandled promise rejection: Invariant Violation: Hooks can only be called inside the body of a function component.
Is there a way to use navigation in a normal function, or anything else.
import React, { useState, useEffect, useCallback } from "react";
import { AsyncStorage, Alert } from "react-native";
import { useNavigation } from "react-navigation-hooks";
export const startMixGame = async (categoryIsChosen, withTimer) => {
const navigation = useNavigation();
if (categoryIsChosen) {
if (withTimer) {
await AsyncStorage.setItem("useTimer", "true");
navigation.navigate({
routeName: "MixedQuestions",
params: {
categoryId: "1"
}
});
} else if (!withTimer) {
// console.log("withTimer", withTimer);
await AsyncStorage.setItem("useTimer", "false");
navigation.navigate({
routeName: "NoTimerMixedQuestions",
params: {
categoryId: "1"
}
});
}
}
};
Thanks
Yes, use a singleton service holding a reference to the navigation, at the root of your app save with a useEffect the reference in the singleton, so you can you it everywhere.
Something like this:
class NavigationService {
constructor() {
this._navigation = null;
}
set navigation(nav) {
this._navigation = nav;
}
get navigation() {
return this._navigation;
}
}
const navigationService = new NavigationService();
export default navigationService;
and in your main screen / view
const HomeScreen = ({navigation}) => {
useEffect(() => {
navigationService.navigation = navigation;
}, [navigation]);
....
and now everywhere you can do this
import navigationService from '../services/navigation';
navigationService.navigation.navigate('Screen');
check this
https://reactnavigation.org/docs/navigating-without-navigation-prop/
you can save a reference to NavigationContainer and use it to navigate.
App
import AppNavigator from './AppNavigator'
...
render (){
return (<AppNavigator/>)
}
AppNavigator
import * as React from 'react';
import AppStack from './AppStack';
import {NavigationContainer} from '#react-navigation/native';
import NavigationService from './NavigationService';
const AppNavigator = () => {
return (
<NavigationContainer
ref={NavigationService.instance}
>
<AppStack/>
</NavigationContainer>
);
};
export default AppNavigator
AppStack
import {createStackNavigator} from '#react-navigation/stack';
const Stack = createStackNavigator();
export default () => {
return (
<Stack.Navigator>
// add all screens here
<Stack.Screen
name={'Home'}
component={HomeCpmponent}
/>
</Stack.Navigator>
)
}
import {NavigationContainerRef} from '#react-navigation/native';
import * as React from 'react';
import {boundClass} from 'autobind-decorator';
#boundClass
class NavigationService {
instance = React.createRef<NavigationContainerRef>();
dispatch(action) {
this.instance.current?.dispatch(action)
}
getInstance() {
return this.instance?.current
}
canGoBack() {
return this.getInstance()?.canGoBack()
}
navigate(routeName: string, params: any) {
return this.getInstance()?.navigate(routeName, params)
}
}
export default new NavigationService()
You can use NavigationService.dispatch() and import {CommonActions, StackActions} from '#react-navigation/native';
or NavigationService.navigate('home',{id:'test1'}) or ...
Actually, now that I see it again, I could just pass the navigation as a argument/parameter, like:
export const startMixGame = async (categoryIsChosen, navigation, withTimer) => { ...}

Resources