React navigation createStackNavigator switch between routes - reactjs

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

Related

React TypeScript containment parent passing props to children

I've been trying this for hours but I haven't found a satisfactory solution. I want to have this wrapper that contains some state that I can then either pass to its child or render something else.
I would like to do something like this abstract example. Is there anything along these lines that I can do?
const MyChild = (props:{state:boolean}) => {
return <Text>`the state is ${props.state}`</Text>
}
const StateWrapper = ({children}:{children:React.ReactNode}) => {
const hookState:boolean|null = useHookState()
if (null) return <Loading />
return {children} <-- with {state:boolean}
}
const App = () => {
return <StateWrapper><MyChild /><StateWrapper>
}
A common pattern for this kind of problem is the "Render Props" approach. The "state wrapper" object takes a prop that passes its data to something else to render. This way you don't have to do any weird changing or copying of state data, and names don't necessarily have to align perfectly, making it easy to swap in other components in the future.
const MyChild = (props: {state: boolean}) => {
return <Text>`the state is ${props.state}`</Text>
}
const StateWrapper = ({children}:{children: (state: boolean) => React.ReactNode}) => {
const hookState:boolean|null = useHookState()
if (null) return <Loading />
return children(state);
}
const App = () => {
return (
<StateWrapper>
{(state) => (<MyChild state={state}/>)}
</StateWrapper>
);
}
See more: https://reactjs.org/docs/render-props.html
3 types of wrapper with ref and added props in typescript:
Sandbox Demo:https://codesandbox.io/s/wrapper-2kn8oy?file=/src/Wrapper.tsx
import React, { cloneElement, forwardRef, useRef, useState } from "react";
interface MyChild extends React.ReactElement {
ref?:React.Ref<HTMLDivElement>|undefined
}
interface MyProps {
children:MyChild|MyChild[],
added:string,
}
const Wrapper1 = ({ children, added }: MyProps) => {
const e =
Array.isArray(children) ?
children.map((child) => {
return cloneElement(child, { added: added })
}) :
cloneElement(children)
return <>
{e}
</>
}
const Wrapper2 = ({ children, added }:MyProps) => {
const e =
Array.isArray(children) ?
children.map((child) => {
return <child.type {...child.props} added={added} ref={child.ref} />
}) :
children?<children.type {...children.props} added={added} ref={children.ref}/>:null
//console.log("2:",e)
if(!Array.isArray(children))console.log(children.ref)
return <>
{e}
</>
}
const Wrapper3 = ({ children, added }:{children:any,added:any}) => {
return <>
{children(added)}
</>
}
const Mydiv = forwardRef((props: any, ref?: any) =>
<div ref={ref}>Origin:{props.message},Added:{props.added ?? "None"}</div>
)
const Container = () => {
const ref1 = useRef<HTMLDivElement>(null)
const ref2 = useRef<HTMLDivElement>(null)
const ref3 = useRef<HTMLDivElement>(null)
const [refresh, setRefresh] = useState(1)
const setColor = () => {
console.log("ref1:", ref1.current, "ref2:", ref2.current, "ref3:", ref3.current)
if (ref1.current) ref1.current.style.color = "red"
if (ref2.current) ref2.current.style.color = "red"
if (ref3.current) ref3.current.style.color = "red"
}
return <>
<button onClick={setColor}>Change Color</button>
<button onClick={() => setRefresh((n) => n + 1)}>Refresh page</button>
<div>{`state:${refresh}`}---state:{refresh}</div>
<Wrapper1 added="Wrapper1 added">
<Mydiv ref={ref1} message={`MyDiv 1 with Ref,parent state:${refresh}`} />
<Mydiv message={`MyDiv 1 without ref,parent state:${refresh}`} />
</Wrapper1>
<Wrapper2 added="Wrapp2 added">
<Mydiv ref={ref2} message={`MyDiv 2 with Ref,parent state:${refresh}`} />
<Mydiv message={`MyDiv 2 without ref,parent state:${refresh}`} />
</Wrapper2>
<Wrapper3 added="Wrapp3 added">
{(added: any) => (<>
<Mydiv ref={ref3} message={`MyDiv 3 with Ref,parent state:${refresh}`} added={added} />
<Mydiv message={`MyDiv 3 without Ref,parent state:${refresh}`} added={added} />
</>
)}
</Wrapper3>
</>
}
export default Container

How to scroll into child component in a list from parent in react?

Hello guys I have an issue that may be simple but I'm stuck.
I have a parent that call an endpoint and render a list of child components once the data is received, at the same time in the URL could (or not) exists a parameter with the same name as the "name" property of one of the child components, so if parameter exists I need to scroll the page down until the children component that have the same "name" as id.
Here is part of the code:
const ParentView = () => {
const [wines, setWines] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const params = new URLSearchParams(document.location.search);
const isMx = params.get('lang') ? false : true;
const wineId = params.get('wine');
const ref = createRef();
const scroll = () => ref && ref.current && ref.current.scrollIntoView({ behavior: 'smooth' });
React.useEffect(() => {
retrieveData();
}, []);
React.useEffect(() => {
if (!isEmptyArray(wines) && !loading && wineId) scroll();
}, [wineId, wines, loading]);
function renderWines() {
if (loading) return <Loading />;
if (isEmptyArray(wines) && !loading) return <h2>No items found</h2>;
if (!isEmptyArray(wines) && !loading)
return (
<React.Fragment>
{wines
.filter(p => p.status === 'published')
.map((w, idx) => (
<ChildComponent
wine={w}
isMx={isMx}
idx={idx}
openModal={openModal}
ref={wineId === w.name.toLowerCase() ? ref : null}
/>
))}
</React.Fragment>
);
}
return (
<React.Fragment>
{renderWines()}
</React.Fragment>
);
};
And this is the child component...
import React, { forwardRef } from 'react';
import { Row,} from 'reactstrap';
const WineRow = forwardRef(({ wine, isMx, idx, openModal }, ref) => {
const {
name,
} = wine;
// const ref = React.useRef();
React.useEffect(() => {
// console.log({ ref, shouldScrollTo });
// shouldScrollTo && ref.current.scrollIntoView({ behavior: 'smooth' });
}, []);
return (
<Row id={name} ref={ref}>
...content that is irrelevant for this example
</Row>
);
});
Of course I remove a lot of irrelevant code like retrieveData() function and all the logic to handle the data from api
I've been trying many ways but I can't make it works :(
Well after a headache I just realized that I don't need react to do this 😂
so I just fixit with vanilla js 🤷🏻‍♂️
Parent:
const Public = () => {
const [wines, setWines] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const params = new URLSearchParams(document.location.search);
const isMx = params.get('lang') ? false : true;
const wineId = params.get('wine');
React.useEffect(() => {
retrieveData();
}, []);
React.useEffect(() => {
if (!isEmptyArray(wines) && !loading && wineId) scroll(wineId);
}, [wineId, wines, loading]);
const scroll = wineId => document.getElementById(wineId).scrollIntoView({ behavior: 'smooth' });
const retrieveData = async () => {
....logic to handle data
};
function renderWines() {
if (loading) return <Loading />;
if (isEmptyArray(wines) && !loading) return <h2>No items found</h2>;
if (!isEmptyArray(wines) && !loading)
return (
<React.Fragment>
{wines
.filter(p => p.status === 'published')
.map((w, idx) => (
<WineRow wine={w} isMx={isMx} idx={idx} />
))}
</React.Fragment>
);
}
return (
<React.Fragment>
{renderWines()}
</React.Fragment>
);
};
and children:
const WineRow =({ wine, isMx, idx,}) => {
const {
name,
} = wine;
return (
<Row id={name.toLowerCase()}>
...content that is irrelevant for this example
</Row>
);
};
And that's it 😂 sometimes we are used to do complex things that we forgot our basis 🤦🏻‍♂️
Hope this help someone in the future

UseAsyncstorage causes infinit loop in react native

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

Problem when dynamically registering routes in an application with microfrontends concept

I have an Typescript + Redux (with RTK) application using the microfrontends concept. All the steps for the construction came from this tutorial: Microfrontends tutorial.
The main component is Microfrontend.tsx (omitted imports):
interface Manifest {
files: {
'main.js': string
'main.js.map': string
'index.html': string
}
entrypoints: string[]
}
const MicroFrontend = ({
name,
host,
module
}: {
name: string
host: string | undefined
module: string
}) => {
const history = useHistory()
useEffect(() => {
const renderMicroFrontend = () => {
// #ts-ignore
window[`render${name}`] && window[`render${name}`](`${name}-container`, history)
}
if (document.getElementById(name)) {
renderMicroFrontend()
return
}
const manifestUrl = `${
isDevProfile ? host : ''
}/${module}/view/asset-manifest.json`
fetch(manifestUrl)
.then(res => res.json())
.then((manifest: Manifest) => {
const script = document.createElement('script')
script.id = name
script.crossOrigin = ''
script.src = `${host}${manifest.files['main.js']}`
script.onload = () => {
renderMicroFrontend()
}
document.head.appendChild(script)
})
return () => {
// #ts-ignore
window[`unmount${name}`] && window[`unmount${name}`](`${name}-container`)
}
})
return (
<main id={`${name}-container`} style={{ height: '100%' }} />
)
}
MicroFrontend.defaultProps = {
document,
window
}
export default MicroFrontend
I'm trying to render the routes of the child components in a dynamic way, however, when I do this, I have a very strange effect: Bug.
The code snippet that generates this effect is this (omitted imports):
const App = () => {
const dispatch = useAppDispatch()
const { loadWithSuccess } = useSelector(moduleSelectors)
const avaibleModuleLinks = useSelector(avaibleModuleLinksWhitoutHome)
useEffect(() => {
dispatch(fetchAvaibleModules()).then(response =>
dispatch(fetchAvaibleModuleLinks(response.payload as string[]))
)
}, [dispatch])
return (
<BrowserRouter>
<Template>
<Switch>
<Route exact={true} path="/" component={Home} />
{loadWithSuccess ? avaibleModuleLinks?.map(
(subMenuPath: SubMenuPath | undefined, index: number) => {
const subMenuPathKey = subMenuPath ? subMenuPath.key : ''
let micro = () => (
<MicroFrontend
module={subMenuPathKey}
host="127.0.0.1"
name={subMenuPath ? subMenuPath.key.charAt(0).toUpperCase() : ''}
/>
)
return (
<Route
key={index}
path={`/dfe/view/${subMenuPathKey}`}
component={micro}
/>
)
}
): <></>}
</Switch>
</Template>
</BrowserRouter>
)
}
export default App
Only when I don't render routes dynamically do I have the desired effect: desired behavior
The code snippet that generates this effect is this (omitted imports):
const ModuleNfe = () => (
<MicroFrontend host="127.0.0.1" name="Nfe" module="nfe" />
)
const App = () => {
const dispatch = useAppDispatch()
const { loadWithSuccess } = useSelector(moduleSelectors)
const avaibleModuleLinks = useSelector(avaibleModuleLinksWhitoutHome)
useEffect(() => {
dispatch(fetchAvaibleModules()).then(response =>
dispatch(fetchAvaibleModuleLinks(response.payload as string[]))
)
}, [dispatch])
return (
<BrowserRouter>
<Template>
<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/dfe/view/nfe" component={ModuleNfe} />
</Switch>
</Template>
</BrowserRouter>
)
}
export default App
As you may have noticed, the desired behavior is for my page to be rendered inside the Template component. But for some reason, this is not the case.

React Hook triggers re-render but React Navigation doesn't update screens

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;

Resources