React-native useContext returns undefined - reactjs

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.

Related

Why am I getting undefine?

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;

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

Adding redux to react native?

i'm currently working on an app using react-native for the first time,
but i'm struggling to add redux.
i get this error TypeError: Cannot read property 'getState' of undefined and i don't know how to fix it.
this my code :
import React from "react";
import Home from "./home";
import { store } from "./redux/store";
import { Provider } from "react-redux";
/* #flow */
import { View, StyleSheet } from "react-native";
class App extends React.Component {
render() {
return (
<Provider store={store}>
<View style={styles.container}>
<Home />
</View>
</Provider>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
store.js :
import { applyMiddleware, createStore } from "redux";
import logger from "redux-logger";
import reducer from "./reducer";
const middlewares = [logger];
const store = createStore(reducer, applyMiddleware(...middlewares));
export default store;
reducer.js :
import { combineReducers } from "redux";
import mapReducer from "../redux/maps/maps-reducer";
export default combineReducers({
map: mapReducer
});
maps-action.js:
import MAPSActionTypes from './maps-action-types';
export const currentlocation = () => ({
console.log(${location});
type : MAPSActionTypes.GET_CURRENT_LOCATION
});
maps-reducer.js:
import MAPSActionTypes from "./mapsactiontypes";
const INITIAL_STATE = {
location: {}
};
const mapReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case MAPSActionTypes.GET_CURRENT_LOCATION:
return {
...state,
location: action.payload
};
default:
return state;
}
}
export default mapReducer;
home.js:
import React from 'react';
import {
StyleSheet,
View,
Text,
} from 'react-native';
import {connect} from 'react-redux'
const Home = (props) => {
return (
<View style={styles.container}>
<Text>welcome</Text>
<Text>{props.location}</Text>
</View>
);
};
const styles = StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center'
}
});
const mapStateToProps = (state) => {
return {
location: state.map.location
}
}
export default connect(mapStateToProps)(Home);
i'm all ears from more clarification or more details.
i will be very thankful if there's anyone who can help me to fix that problem.
The problem is you export default in store.js file.
export default store;
But in App you import it with {}. So the store you imported is undefined.
import { store } from "./redux/store";
The correct import is
import store from "./redux/store";

Resources