I wrote a useAuth hook. From this hook I use the var partnerId in my App.js to build the StackNavigator Screens. I expect, when I set/unset partnerId that the user gets automatically navigated to the Pairing or Dashboard component, because React Navigation will remove one of it. (As described here after the first code snippet)
So when I run the App, I see the dashboard, because I used addPartner('test');.
When I click the button on the dashboard, I see that the component re-rendered because the <Text> node changed from Partner to No Partner.
But I expect to get redirected to the Pairing component!
App.js
const AppStack = createStackNavigator();
const App = () => {
const [myId, partnerId, addPartner, removePartner] = useAuth();
addPartner('test'); // for testing right now
return (
<NavigationContainer>
<AppStack.Navigator>
{!partnerId ? (
<AppStack.Screen
name="Pairing"
component={Pairing}
options={{headerShown: false}}
/>
) : (
<AppStack.Screen
name="Dashboard"
component={Dashboard}
options={{title: 'Dashboard'}}
/>
)}
</AppStack.Navigator>
</NavigationContainer>
);
};
export default App;
dashboard.js
const Dashboard = () => {
const [myId, partnerId, addPartner, removePartner] = useAuth();
return (
<SafeAreaView>
<ScrollView>
<Text>Dashboard</Text>
{partnerId ? <Text>Partner</Text> : <Text>No Partner</Text>}
<Button
title="Remove Partner Id"
onPress={() => {
removePartner();
}}
/>
</ScrollView>
</SafeAreaView>
);
};
export default Dashboard;
useAuth.js
const PARTNERID_KEY = 'partnerId';
const MYID_KEY = 'myId';
const useAuth = () => {
const [myId, setMyId] = useState(null);
const [partnerId, setPartnerId] = useState(null);
const removePartner = async () => {
await AsyncStorage.removeItem(PARTNERID_KEY);
setPartnerId(null);
console.log('Removed Partner', partnerId);
};
const addPartner = async (newPartnerId) => {
if (!newPartnerId) {
console.error('newPartnerId is null/undefined');
return false;
}
if (newPartnerId.toUpperCase() === myId.toUpperCase()) {
console.error('newPartnerId and myId are equal');
return false;
}
await AsyncStorage.setItem(PARTNERID_KEY, newPartnerId);
setPartnerId(newPartnerId);
console.log('Added Partner', partnerId);
return true;
};
useEffect(() => {
const init = async () => {
// partner
const storagePartnerId = await AsyncStorage.getItem(PARTNERID_KEY);
if (storagePartnerId) {
setPartnerId(storagePartnerId);
}
// self
let storageMyId = await AsyncStorage.getItem(MYID_KEY);
if (!storageMyId) {
storageMyId = await UUIDGenerator.getRandomUUID();
await AsyncStorage.setItem(MYID_KEY, storageMyId);
}
setMyId(storageMyId);
console.log('Auth init', myId, partnerId);
};
init();
}, [myId, partnerId]);
return [myId, partnerId, addPartner, removePartner];
};
export default useAuth;
I found out that it's not possible to use a Hook like a global singleton (e.g. like a Angular Service), because it is not a singleton!
The answer is to call useAuth() once in App.js and use React.createContext() to pass the values to child components.
App.js
const AppStack = createStackNavigator();
export const AuthContext = React.createContext();
const App = () => {
const [myId, partnerId, addPartner, removePartner] = useAuth();
const authContext = {myId, partnerId, addPartner, removePartner};
addPartner('test'); // testing
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
<AppStack.Navigator>
{!partnerId ? (
<AppStack.Screen
name="Pairing"
component={Pairing}
options={{headerShown: false}}
/>
) : (
<AppStack.Screen
name="Dashboard"
component={Dashboard}
options={{title: 'Dashboard'}}
/>
)}
</AppStack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
};
export default App;
dashboard.js
const Dashboard = () => {
const foo = React.useContext(AuthContext);
return (
<SafeAreaView>
<ScrollView>
<Text>Dashboard</Text>
{foo.partnerId ? <Text>Partner</Text> : <Text>No Partner</Text>}
<Button
title="Remove Partner Id"
onPress={() => {
foo.removePartner();
}}
/>
</ScrollView>
</SafeAreaView>
);
};
export default Dashboard;
Related
I'm making movie app (using react.js)
I want to show a list of new movies whenever user scrolls down.
but when i write these codes, it doesn't work.
I used react-intersection-observer and made the second useEffect for adding new list.
can you see what is the problem...?
**import { useInView } from "react-intersection-observer";**
import { useEffect, useRef, useState } from "react";
import Movie from "../components/Movie";
import HeaderComponent from "../components/HomeButton";
import GlobalStyle from "../GlobalStyle";
import { Route, useParams } from "react-router-dom";
import { LoadingStyle, ListContainer } from "../components/styles";
function Home() {
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const [movieSearch, setMovieSearch] = useState("");
const [movieName, setMovieName] = useState("");
const [pageNumber, setPageNumber] = useState(1);
** const { ref, inView } = useInView({
threshold: 0,
});**
const param = useParams();
const getMovies = async () => {
const json = await (
await fetch(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=1&page=${pageNumber}&query_term=${movieName}&sort_by=year`
)
).json();
setMovies(json.data.movies);
setLoading(false);
};
const onChange = event => {
setMovieSearch(event.target.value);
};
const onSubmit = event => {
event.preventDefault();
if (typeof param === Object) {
setMovieName(movieSearch);
getMovies();
} else {
Route(`/main`);
}
};
// When User Searching...
useEffect(() => {
getMovies();
}, [movieName]);
// When User Scroll, Keep Adding Movies at the bottom...
** useEffect(() => {
setPageNumber(prev => prev + 1);
setMovies(prev => {
return [...movies, ...prev];
});
getMovies();
}, [inView]);
**
return (
<>
<GlobalStyle />
<HeaderComponent
onSubmit={onSubmit}
onChange={onChange}
movieSearch={movieSearch}
/>
{loading ? (
<LoadingStyle>Loading...</LoadingStyle>
) : (
<>
<ListContainer>
{movies.map(item => {
return (
<Movie
key={item.title}
id={item.id}
title={item.title}
year={item.year}
medium_cover_image={item.medium_cover_image}
rating={item.rating}
runtime={item.runtime}
genres={item.genres}
summary={item.summary}
/>
);
})}
</ListContainer>
**{inView ? <>๐ญ</> : <>๐งถ</>}**
<div ref={ref} style={{ width: "100%", height: "20px" }}></div>
</>
)}
</>
);
}
export default Home;
and, when i debug this code, this errors comes out.
react_devtools_backend.js:4026 Warning: Encountered two children with the same key, `Headless Horseman`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted โ the behavior is unsupported and could change in a future version.
at div
at O (http://localhost:3000/static/js/bundle.js:47415:6)
at Home (http://localhost:3000/static/js/bundle.js:908:80)
at Routes (http://localhost:3000/static/js/bundle.js:41908:5)
at Router (http://localhost:3000/static/js/bundle.js:41841:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:40650:5)
at App
Problem is that my component content rendering in an infinite loop.
const CustomDrawer =({navigation})=>{
const [logged_in , setLoggedIn]= useState(false)
useEffect(
() => {
useAsyncStorage.getItem('auth_token')
.then((token) => {
if(token){
setLoggedIn(true)
}
})
} , []
)
const SignOut = ()=>{
useAsyncStorage.removeItem('auth_token')
.then(()=>{
setLoggedIn(false)
})
}
return(
<View>
<DrawerHeader username = 'Danish hello' /> // Custom drawer header
<View style={styles.DrawerBody}>
<CustomDrawerLink name="Home" iconName='home' navigationScreen='HomeScreen' navigation={navigation} />
{
logged_in ?
<View>
<CustomDrawerLink name="Profile" iconName='user' /> // Drawer custom buttons
<CustomDrawerLink name="Cart" iconName='hamburger' />
</View>
: undefined
}
<Divider/>
{
logged_in ?
<TouchableOpacity style={{flexDirection:'row' , alignItems:'center'}} onPress={()=>{SignOut()}} >
<FontAwesome5 name='sign-out-alt' style={{fontSize:20, marginRight:10 , color:COLORS.red_color, width:35}} />
<Text style={{fontSize:16 , color:'gray'}}>Sign Out</Text>
</TouchableOpacity>
:
<View>
<CustomDrawerLink name="Sign In" iconName='sign-in-alt' navigationScreen='LoginScreen' navigation={navigation} />
<CustomDrawerLink name="Create New Account" iconName='user-plus' navigationScreen='RegisterScreen' navigation={navigation} />
</View>
}
</View>
</View>
)
}
Edited part
ADDED PARENT COMPONENT Here it is
As you mentioned i am using useEffect Hook in my parent component, Here is the code
here i am making Drawer (Side navigation bar)
const Drawer = createDrawerNavigator()
Here is App component
const App = () =>{
const network = useNetInfo()
const [Scren_navigation , setNavigation] = useState('')
const [activeDrawer , setActiveDrawer] = useState(<EmptyDrawer/>)
const UpdateScreen = (props) =>{
setNavigation(props.navigation)
return activeDrawer
}
useEffect(()=>{
setTimeout(() => {
network.isInternetReachable ? setActiveDrawer(<CustomDrawer navigation={Scren_navigation} />) : setActiveDrawer(<NoInternetDrawer navigation={Scren_navigation} />)
}, 5000);
})
return(
<NavigationContainer>
<Drawer.Navigator drawerContent={({navigation}) => <UpdateScreen navigation={navigation} /> } >
<Drawer.Screen name="StackNavigation" component={Navigation} />
</Drawer.Navigator>
</NavigationContainer>
)
}
I have added parent Component, Please let me know where i was doing wrong things
The package provides two API's one with AsyncStorage and the other one is useAsyncStorage. Both have different usage patterns, you have mixed both in your snippet. Checkout the code below for example usage of each API.
AsyncStorage
const CustomDrawer =({navigation})=>{
const [logged_in , setLoggedIn]= useState(false)
useEffect(
() => {
AsyncStorage.getItem('auth_token')
.then((token) => {
if(token){
setLoggedIn(true)
}
})
} , []
)
const SignOut = ()=>{
AsyncStorage.removeItem('auth_token')
.then(()=>{
setLoggedIn(false)
})
}
return ...;
}
UseAsyncStorage
const CustomDrawer =({navigation})=>{
const [logged_in , setLoggedIn]= useState(false);
const { getItem, setItem, removeItem } = useAsyncStorage('#storage_key');
useEffect(
() => {
getItem('auth_token')
.then((token) => {
if(token){
setLoggedIn(true)
}
})
} , []
)
const SignOut = ()=>{
removeItem('auth_token')
.then(()=>{
setLoggedIn(false)
})
}
return ...
}
The second problem is caused by the parent component because of a missing [] argument to the useEffect:
const App = () => {
const network = useNetInfo()
const [isRunningAvailabilityCheck, setIsRunningAvailabilityCheck] = useState(true);
const [internetIsAvailable, setInternetIsAvailable] = useState(true);
useEffect(() => {
setTimeout(() => {
setInternetIsAvailable(network.isInternetReachable);
setIsRunningAvailabilityCheck(false);
}, 5000);
}, []);
return (
<NavigationContainer>
<Drawer.Navigator drawerContent={({navigation}) => {
if(isRunningAvailabilityCheck){
return <EmptyDrawer/>;
}
if(internetIsAvailable){
return <CustomDrawer navigation={navigation} />
}
return <NoInternetDrawer navigation={navigation} />
}}>
<Drawer.Screen name="StackNavigation" component={Navigation}/>
</Drawer.Navigator>
</NavigationContainer>
)
}
Async storage Docs
First I created a .js file and created context.
In app.js
export default function App({ navigation }) {
const [ItemURL,setItemURL] = useState('URL')
return (
<ItemContext.Provider value={ItemURL}>
...
</ItemContext.Provider>
)
}
now I want to pass my setItemURL to my child component
So I tried.
export default const ItemsComponent = (props) => {
const [URL, setURL] = useContext(ItemContext)
return(
<TouchableWithoutFeedback
onPress={() => {
setURL(props.Json.Image)
}}
/>
)
}
but its not working and saying setURL is not a function(in setURL(props.Json.Image)) ,setURL is 'R'
You should actually pass the setURL function in the context value as well.
export default function App({ navigation }) {
const [ItemURL, setItemURL] = useState('URL');
const value = [ItemURL, setItemURL];
return (
<ItemContext.Provider value={value}>
...
</ItemContext.Provider>
)
}
I'm using React native with Expo and i'm trying to make a dynamic redirect navigation in my component.
When the user come to my page I want to check an async value and with the result of this value I want the user to be redirected to one of the Screen.
I'm using AppLoading from expo and redux state. But it's seems that I can't use navigate at this moment on my Stack.
Here is my code :
const Stack = createStackNavigator();
export default function Mynavigation({ navigation }: BasicNavigationProps) {
const dispatch = useDispatch()
const { appLoading } = useSelector(userStateSelector)
const navigateToRoute = async () => {
const goToPage2 = await needToGoPage2()
if (goToPage2) {
navigation.navigate('Page2')
} else {
navigation.navigate('Page1')
}
}
return (
appLoading ? (
<Stack.Navigator initialRouteName="Page1" headerMode="none">
<Stack.Screen name="Page1" component={Page1}/>
<Stack.Screen name="Page2" component={Page2}/>
</Stack.Navigator>
) : (
<AppLoading
startAsync={ navigateToRoute }
onFinish={() => dispatch(saveAppLoading(false))}
onError={() => {
console.log('error')
}}
/>
)
)
}
Did I forgot something ?
I Implemented another solution with two different Stack navigation, but I don't know witch version is better to use ?
const Stack1 = createStackNavigator();
const Stack2 = createStackNavigator();
export default function Mynavigation({ navigation }: BasicNavigationProps) {
const dispatch = useDispatch()
const [check, setCheck] = useState(false)
const { appLoading } = useSelector(userStateSelector)
const navigateToRoute = async () => {
const goToPage2 = await needToGoPage2()
setCheck(goToPage2)
}
return (
appLoading ? (
check ? (
<Stack1.Navigator initialRouteName="Page1" headerMode="none">
<Stack1.Screen name="Page1" component={Page1Stack1}/>
<Stack1.Screen name="Page2" component={Page2Stack1}/>
</Stack1.Navigator>
) : (
<Stack2.Navigator initialRouteName="Page1" headerMode="none">
<Stack2.Screen name="Page1" component={Page1Stack2}/>
<Stack2.Screen name="Page2" component={Page2Stack2}/>
</Stack2.Navigator>
)
) : (
<AppLoading
startAsync={ navigateToRoute }
onFinish={() => dispatch(saveAppLoading(false))}
onError={() => {
console.log('error')
}}
/>
)
)
}
Thanks for your help
You are right. You can't use navigation props at this stage.
Remove it from the component props and try doing it like this:
const Stack = createStackNavigator();
export default function Mynavigation({ navigation }: BasicNavigationProps) {
const [initialRouteName, setInitialRouteName] = React.useState('');
const [loading, setLoading] = React.useState(true);
const navigateToRoute = async () => {
const goToPage2 = await needToGoPage2()
if (goToPage2) {
setInitialRouteName('Page2')
} else {
setInitialRouteName('Page1')
}
setLoading(false);
}
return (
loading === false && initialRouteName.length > 0 ? (
<Stack.Navigator initialRouteName={initialRouteName} headerMode="none">
<Stack.Screen name="Page1" component={Page1} />
<Stack.Screen name="Page2" component={Page2} />
</Stack.Navigator>
) : (
<AppLoading
startAsync={navigateToRoute}
onFinish={() => dispatch(saveAppLoading(false))}
onError={() => {
console.log('error')
}}
/>
)
)
}
I'm trying to pass a value down using useContext as below
This is Context.js
export const selectedContext = React.createContext();
export const SelectProvider = () => {
return (
<selectedContext.Provider value={"Team One"}>
<Cards />
<Pies />
</selectedContext.Provider>
);
};
I'm calling the context in one of the components like so
This is in Card.js (a child in the provider)
const value = React.useContext(selectedContext);
console.log(value);
When I initialize the value from React.createContext, the value is passed down to my component but when I try using the provider it doesn't work.
What am I doing wrong?
When you are using React.useContext like this it's not wire into the <Context.Provider>
Please see the docs on who to use React.useContext here.
It's seems that the React.useContext will not work with in the Provider direct component children, so you need to make one more component in between. (like in the docs example)
const selectedContext = React.createContext();
const SelectProvider = () => {
return (
<selectedContext.Provider value={"Team One"}>
<Cards />
</selectedContext.Provider>
);
};
const Cards = () => {
const value = React.useContext(selectedContext);
console.log(value); // will not work
return (
<Card />
);
};
const Card = () => {
const value = React.useContext(selectedContext);
console.log(value); // will work
return (
<div>My Card</div>
);
};
If you need it to work on the first layer of component you can use <Context.Consumer> and it will work within.
const selectedContext = React.createContext();
const SelectProvider = () => {
return (
<selectedContext.Provider value={"Team One"}>
<Cards />
</selectedContext.Provider>
);
};
const Cards = () => {
const value = React.useContext(selectedContext);
console.log(value); // will not work
return (
<div>
<selectedContext.Consumer>
{({value}) => (
<h1>{value}</h1> // will work
)}
</selectedContext.Consumer>
</div>
);
};
Your code is fine, but you should "call the context" in the child component of the provider, as the value is available in Provider's children:
export const SelectedContext = React.createContext();
export const SelectProvider = ({ children }) => {
return (
<SelectedContext.Provider value={'Team One'}>
{children}
</SelectedContext.Provider>
);
};
const ProviderChecker = () => {
const value = React.useContext(SelectedContext);
return <div>{value}</div>;
};
const App = () => {
return (
<SelectProvider>
<ProviderChecker />
</SelectProvider>
);
};