React Native useContext results are undefined - react-native

I am trying to useContext provider in my midiMonitor function. The midi monitor function is a helper function. The issue is that the context results as undefined because it's not inside the profile context. I am trying to figure out how to give the midiMonitor access to the profileContext. I know it can be done if I import the helper function inside the home component however I don't want to import it in the home component because it has nothing the do with the home component.
Is there another way I can use the midiMonitor helper function and have access to the contents of the profileContext
const App = () => {
midiMonitor()
return(
<ProfileProvider>
<Home />
</ProfileProvider>
)
}
const Home = () => {
// some functions that have access to the Profile Provider
const {profileName} = useContext(ProfileContext)
return(
<View>
<Text>{profileName}</Text>
</View>
)
}
const midiMonitor = () => {
const {profileName} = useContext(ProfileContext)
if (profileName === 'default'){
// results are undefined. I know why but do not want to
//import in into the Home component as it has nothing to do with the home component
console.log('you are using default midi profile')
}
}

It would have been easier if you could "ProfileContext" and "ProfileProvider". Here is an example of the same. Hope this helps.
import { useState, createContext, useContext } from "react";
import ReactDOM from "react-dom/client";
const ProfileContext = createContext();
function Component1() {
const [user, setUser] = useState("Jesse Hall");
return (
<ProfileContext.Provider value={user}>
<h1>{`Hello ${user}!`}</h1>
<Component2 user={user} />
</ProfileContext.Provider>
);
}
function Component2() {
return (
<>
<h1>Component 2</h1>
<Component3 />
</>
);
}
function Component3() {
return (
<>
<h1>Component 3</h1>
<Component4 />
</>
);
}
function Component4() {
return (
<>
<h1>Component 4</h1>
<Component5 />
</>
);
}
function Component5() {
const user = useContext(ProfileContext);
return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component1 />);

Related

React Native Root Element, deciding on async call

I'm currently writing an App in React-Native, which also includes a login. I use AsyncStorage for saving the credentials. Now I want to show the user different Screens (Navigators) whether he is logged in or not.
To check if he is logged in, I check if there are credentials in the AsyncStorage, and the function to check this returns a promise. So now when I call the function in my component, it wont wait until the promise has resolved and I don't have any idea on how to solve. I tried with but this also failed. Maybe you have any idea. Below my code. Thanks
import 'react-native-gesture-handler'
import { NavigationContainer } from '#react-navigation/native'
import AppNavigation from './navigation/AppNavigation.js'
import { ThemeProvider, Text } from 'react-native-magnus'
import { useState, useEffect, useCallback, Suspense} from 'react'
import {React } from 'react'
import getNutrientsCompare from './utils/getNutrientsCompare.js'
import getLoginSession from './utils/getLoginSession.js'
import Login from './pages/Login.js'
import { ActivityIndicator } from 'react-native'
const wait = (timeout) => {
return new Promise(resolve => setTimeout(resolve, timeout));
}
const RootElement = () => {
const [result, setResult] = useState(null)
getLoginSession().then(data => {
[loginSessionState, setLoginSessionState] = useState("");
if (loginSessionState != null) {
setResult((
<ThemeProvider>
<NavigationContainer >
<AppNavigation />
</NavigationContainer>
</ThemeProvider>))
} else {
setResult((
<ThemeProvider>
<Login>
</Login>
</ThemeProvider>
))
}
})
return result
}
const App = () => {
return (
<Suspense fallback={<ActivityIndicator />}>
<RootElement />
</Suspense>
)
}
export default App
Give this a try
import { ActivityIndicator } from "react-native";
const RootElement = () => {
const [loggedIn, setLoggedIn] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
try {
const data = await getLoginSession();
if (data != null) {
setLoggedIn(true);
}
} catch (error) {
setLoggedIn(false);
}
setLoading(false);
})();
}, []);
return (
<>
{!loading ? (
loggedIn ? (
<ThemeProvider>
<NavigationContainer>
<AppNavigation />
</NavigationContainer>
</ThemeProvider>
) : (
<ThemeProvider>
<Login />
</ThemeProvider>
)
) : (
<ActivityIndicator size="large" color="#00ff00" />
)}
</>
);
};

React-Native Redux connect syntaxError

I'm learning Redux. I don't konw why syntax error appear.
I'm building a simple counter app.
This is my code. I don't well react-native and redux. so I really need help.Plase help me.
I don't know where I'm wrong. I think problem is View.js.
App.js
`import React from 'react';
import Screen from './src/View';
import { Provider,createStore } from 'react-redux';
const store = createStore(reducers);
const App=()=>{
return(
<Provider store={store}>
<Screen/>
</Provider>
)
}
export default App;`
View.Js
This part have syntax error.
import React from "react";
import { StyleSheet,View, Text, Button } from "react-native";
import { connect } from 'react-redux';
export default function Screen (){
return(
<View >
<Text style={styles.containers}>counter: </Text>
<Text style={styles.containers}>{this.props.state.counterNum}</Text>
<Button title="+" >+</Button>
<Button title="-" >-</Button>
</View>
);
};
const styles=StyleSheet.create({
containers : {
textAlign:"center",
}
}
);
function mapStateToProps(state){
return {
state: state.counterNum
};
}
// Actions을 props로 변환
function matchDispatchToProps(dispatch){
return bindActionCreators({
counter : counterNum
}, dispatch);
}
//(error here)
export default connect(mapStateToProps, matchDispatchToProps)(Screen)
index.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
const initialState={
counter:[
{
counterNum:0,
},
],
};
const counter=( state= initialState, action)=>{
const {counter}=state;
switch(action.type){
case 'INCREMENT':
return({
counter:[
...counter.slice(0,action.index),
{
counterNun : counter[action.index].counterNum +1,
}
]
});
case 'DECREMENT' :
return({
counter:[
...counter.slice(0,action.index),
{
counterNun : counter[action.index].counterNum -1,
}
]
});
default :
return state;
}
}
export default counter;
Any help/knowledge will be appreciated thanks!
It looks like you are trying to use syntax for class components.
Try passing props into Screen like this:
export default function Screen (props){
return(
<View >
<Text style={styles.containers}>counter: </Text>
<Text style={styles.containers}>{props.state}</Text>
<Button title="+" >+</Button>
<Button title="-" >-</Button>
</View>
);
};
no need for this keyword
Also, would be better to do this:
function mapStateToProps(state){
return {
counterNum: state.counterNum // <-- change property name to counterNum
};
}
then you can access this value with props.counterNum

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>
</>
);
}

useRef to control a component that is inside child component

I am developing react-native project.
I have created a custom component inside which I use a 3rd party library component. The 3rd party library component needs the useRef hook from my code so that I can control the library component.
import React, {useRef} from 'react';
...
const MyComponent = () => {
const ref = useRef();
return (
<View>
<Button
title="OPEN 3rd party component"
onPress={() => ref.current.open()}
/>
<LibraryComponent
ref={ref}
...
/>
</View>)
}
Now, I have a screen named MyScreen, I need to show MyComponent on MyScreen, and control that library component from MyScreen instead of MyComponent. So, I refactored MyComponent to this (I removed the Button and change ref to be a pass-in property):
const MyComponent = ({ref}) => {
return (
<View>
<LibraryComponent
ref={ref}
...
/>
</View>)
}
Then, in MyScreen :
import React, {useRef} from 'react';
import MyComponent from '../components/MyComponent';
export const MyScreen = () => {
const screenRef = useRef();
...
return (
<View style={styles.container}>
<Button onPress=>{()=>screenRef.current.open()}/>
<MyComponent ref={screenRef}>
...
</View>
)
}
But I get warning message: Function components cannot be give refs. Attempts to access this ref will fail. Did you mean to use React.ForwardRef() ?
My questions is:
How to get rid of that warning & having MyScreen control the library component with reference hook?
======== UPDATE =======
I changed the name of the property in MyComponent from ref to innerRef like #Rishabh Anand suggested. The warning disappeared.
const MyComponent = ({innerRef}) => {
...
}
MyScreen is now:
export const MyScreen = () => {
const screenRef = useRef();
...
return (
<View style={styles.container}>
<Button onPress=>{()=>screenRef.current.open()}/>
<MyComponent innerRef={screenRef}>
...
</View>
)
}
But now a new issue comes, when I click on the Button of MyScreen, app crashed with error undefined is not an object (evaluating screenRef.current.open). It seems the screenRef.current holds a null instead of the library component. Why?
You have to use React.forwardRef
From the docs: https://fr.reactjs.org/docs/forwarding-refs.html
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
const ref = React.createRef();
<FancyButton ref={ref}>Cliquez ici</FancyButton>;
You can after you set a ref, call some function inside your child component like this:
const FancyButton = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
click: () => {
onClick();
},
}));
const onClick = () => {
console.log('Clicked.');
};
return (
<button onClick={onClick} className="FancyButton">
{props.children}
</button>
);
});
const ref = React.createRef();
<FancyButton ref={ref}>Cliquez ici</FancyButton>;
And call from your parent component
ref.current.click()

Unable to load provider from react-redux module in react native

I am creating a slide bar, In that, I have used the react-redux library. When I call the class which contains the redux-code, it works fine. I want to show this slide bar after login. Therefore, with conditions (I set a state variable if user login successfully then only this page should get rendered), I tried to call the same file which shows a blank page. I printed the console log. I am able to print all the logs. But with conditions, I am not able to load the data.
I don't know much about react-redux.Can you assist me to resolve this?
My code is,
main.js,
import React, {Component} from 'react';
import {
StyleSheet,
Dimensions,
Platform,
View,
StatusBar,
DrawerLayoutAndroid,
} from 'react-native';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from '../Redux/reducers';
import { setNavigator, setActiveRoute } from "../Redux/actions";
import DrawerContent from '../Navigation/DrawerContent';
import Toolbar from '../Navigation/Toolbar';
import AppNavigation from '../Navigation/AppNavigation';
import { bgStatusBar, bgDrawer } from '../global.styles';
let store = createStore(reducer);
/* getDrawerWidth Default drawer width is screen width - header width
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
const getDrawerWidth = () => Dimensions.get('window').width - (Platform.OS === 'android' ? 56 : 64);
export default class Main extends Component {
constructor() {
super();
this.drawer = React.createRef();
this.navigator = React.createRef();
}
componentDidMount() {
store.dispatch(setNavigator(this.navigator.current));
}
openDrawer = () => {
this.drawer.current.openDrawer();
};
closeDrawer = () => {
this.drawer.current.closeDrawer();
};
getActiveRouteName = navigationState => {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getActiveRouteName(route);
}
return route.routeName;
};
render() {
return (
<Provider store={store}>
<DrawerLayoutAndroid
drawerWidth={getDrawerWidth()}
drawerPosition={DrawerLayoutAndroid.positions.Left}
renderNavigationView={
() => <DrawerContent closeDrawer={this.closeDrawer} />
}
ref={this.drawer}
>
<View style={styles.container}>
<StatusBar
translucent
animated
/>
<Toolbar showMenu={this.openDrawer} />
<AppNavigation
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = this.getActiveRouteName(currentState);
store.dispatch(setActiveRoute(currentScreen));
}}
ref={this.navigator}
/>
</View>
</DrawerLayoutAndroid>
</Provider>
);
}
}
Login.js
import Main from './main';
render() {
return (
<View>
{this.state.isLoggedIn ?
<Main/>
:
<ChangePassword isUpdatePassword={this.state.isUpdatePassword} callLogin={this.callLogin}/>
);
}
}
If I just call Main class inside render method it works. But It does not work with conditions.