React-Native navigation doesn't recognize params - react-native

I have a typescript react-native application. I have used navigation with some sucess but in this case, no matter what I do, the id, filename, and file are all undefined.
Here is the code with the issue. I know according to react-native navigation doing what I'm doing with the file isn't necessary great coding practice, but this is just displaying a file, so it's not a huge deal. (I am storing the filename and id in a sqlite database). I added the useState hoping that the file gets passed or change that it can change the state.
export type Props = {
navigation: PropTypes.func.isRequired;
id:PropTypes.int.isRequired;
filename:Protypes.string.isRequired;
file:{
name: PropTypes.string.isRequired;
uri: PropTypes.path.isRequired;
type: PropTypes.mime.isRequired};
};
const FileViewScreen: React.FC<Props> = ({navigation,id,filename,file}) => {
console.log("File View Screen?")
console.log("currentFile");
console.log(id)
console.log(currentFile)
console.log(filename)
console.log(file)
const [currentFile,setCurrentFile] = useState(file);
Here is where the user gets routed to the FileScreen. Here I was testing to see if any id is passed, I'm aware that the id needs changed to the id and not 1 but this was testing.
const HomeScreen: React.FC<Props> = ({navigation}) => {
const [loading, setLoading] = useState(false);
const [file, setFile] = useState({});
const [files, setFiles] = useState([]);
const downloadFile = async () => {
try {
...
const newEntry = {
name: 'ImageFileName' + Math.random().toString(),
uri: result.path,
type: result.mime,
};
const res = await addFile(result.path);
console.log(res)
navigation.navigate('FileView', { id:1,filename:res,file:newEntry });
} catch (error) {
console.log('downloadFile error', error);
}
};
return (
<View style={styles}>
<Text>Welcome Home</Text>
{loading && <ActivityIndicator size={'large'} color="#000" />}
{!loading && (
<>
<Button
title="Start Recording"
onPress={downloadFile}
/>
Here is the addFile function. I don't think this matters but I've been wrong before. Here
export const addFile = file_path => {
db.transaction(txn => {
console.log("db transaction")
console.log(file_path)
const response = txn.executeSql(
'INSERT INTO files(file_path,uploaded) VALUES (' +
file_path +
',' +
false +
')',
(sqlTxn, res) => {
console.log("adding")
console.log(`${file_path} video added successfully`);
return file_path;
},
error => {
console.log('error on adding file ' + error.message);
return 0;
},
);
});
console.log(resopnse)
};
In my app.js (i do have a working register and, login, home screen. Right now this is the only time I have an issue.
<NavigationContainer>
<Stack.Navigator initialRouteName={initalRoute}>
<Stack.Screen name="Login">
{props => (
<LoginScreen {...props} setToken={setUserToken} setUser={setUser} />
)}
</Stack.Screen>
<Stack.Screen name="Home">
{props => (
<HomeScreen {...props}/>
)}
</Stack.Screen>
<Stack.Screen name="Register" component={RegisterScreen} />
<Stack.Screen name="FileView">
{props =>(
<FileViewScreen {...props} />
)}
</Stack.Screen>
</NavigationContainer>
Things that I've tried.
I tried to change the RecordingView in app.js to make sure it's specifically passing props
I've changed props to be only an id, only a filename, or only the newentry.
I've tried to set the state as the file in case it gets passed later.
Things that I haven't tried
I haven't put this in a button. That's the main thing I haven't been able to find if navigation.navigate only works on a push event. I don't see any documentation stating that.

If your FileViewScreen is a child component of some parent view then id,filename,file will be available from component props object. If instead you navigate to FileViewScreen from another screen then id,filename,file will be part of route prop.
To account for both use cases you could so something like this
const FileViewScreen: React.FC<Props> = (props) {
// try extracting props from root prop object
let { id,filename,file } = props;
// if navigation route params are available,
// then extract props from route.params instead
// you could also check if id, filename, file etc are null
// before extracting from route.params
const { route } = props;
if (route && route.params) {
({ id,filename,file } = route.params);
}
...
}

Related

how to set UI component to state in background in react-native?

I am new in react-native, in my application I am generating QRCode by one library and it working but in android it is taking time to show on UI, when I set that component to hook to show on UI then it stuck for while and every thing getting non-responsive. After some time it shows and everything work well.
So how can put that setWalletQR in background so that I can show loader until it show to UI?
Here is my code where I am generating the QR in InteractionManager to show
const PozReceive = ({ onClose }: ReceiveProps) => {
const [walletQR, setWalletQR] = useState<ConentQR>(null);
const generateWalletQrCode = () => {
const interactionPromise = InteractionManager.runAfterInteractions(() => {
const qrCode = ConentQR(user?.walletAddress || '', walletImg, 50);
setWalletQR(qrCode);
});
return () => interactionPromise.cancel();
};
useEffect(() => {
if (!pouchQR) {
generatePouchQrCode();
}
}, []);
return (
<Modal
coverScreen={true}
isVisible={true}
onBackdropPress={onClose}
onBackButtonPress={onClose}
backdropColor={Colors.DARK_PURPLE}
backdropOpacity={0.7}
style={styles.modal}>
<>
<BlurView
style={styles.blurView}
blurType="dark"
blurAmount={20}
reducedTransparencyFallbackColor="white"
/>
<VStack style={[styles.modalContainer]}>
{!walletQR ? (
<Image style={styles.qrLoader} source={loaderGif} />
) : (
walletQR
)}
</VStack>
</>
</Modal>
);
};
and here is QR code generator code :-
const ContentQR = (
content: string,
logo: Image.propTypes.source,
logoSize: number,
backgroundColor: string = 'transparent',
) => {
return (
<QRCode
color={Colors.DARK_PURPLE}
content={content}
codeStyle={'dot'}
outerEyeStyle={'diamond'}
logo={logo}
logoSize={logoSize}
backgroundColor={backgroundColor}
/>
);
};
Someone please help me I getting stuck here for while.
You can introduce a variable isLoading and render the loader based on this variable instead of qr value.
const PozReceive = ({ onClose }: ReceiveProps) => {
const [walletQR, setWalletQR] = useState<ConentQR>(null);
const [isLoading, setIsLoading] = useState<Boolean>(false);
const generateWalletQrCode = () => {
setIsLoading(true)
const interactionPromise = InteractionManager.runAfterInteractions(() => {
const qrCode = ConentQR(user?.walletAddress || '', walletImg, 50);
setWalletQR(qrCode);
setIsLoading(false)
});
return () => interactionPromise.cancel();
};
....
<VStack style={[styles.modalContainer]}>
{isLoading && <Image style={styles.qrLoader} source={loaderGif} />}
{!isLoaing && walletQR && walletQR}
</VStack>

Flatlist inside tab navigator is scrolling to top on state change in react native

Here you can see the gif
Here is my whole Navigator functional component. I'm trying to implement two tabs using Tab Navigator. One to display the cryptos and the other to display the forex data.
The problem is, when I try to load more data on reaching the flatlist's end, the flatlist is scrolling to the top since I'm making a state change [page+1].
const Navigator = () => {
const Tab = createMaterialTopTabNavigator();
const renderItems = ({ item }) => (
<Text>{item.name}<Text>
);
const fetchMarketData = async () => {
console.log("Fetching");
const marketData = await getCryptoMarketData({ page });
if (marketData != "Network Error") {
const ids = data.map((item) => item.id);
let newData = marketData.filter((item) => !ids.includes(item.id));
setData([...data, ...newData]);
setFetching(false);
} else {
setFetching(false);
Alert.alert(marketData, "Sorry for the inconvenience");
}
};
useEffect(() => {
setFetching(true);
const data = async () => {
await fetchMarketData();
};
}, [page]);
const handleLoadMore = async () => {
setFetching(true);
setPage((page) => page + 1);
};
const ScreenA = () => (
<FlatList
data={data}
style={{ backgroundColor: "white" }}
keyExtractor={(item) => item.id}
renderItem={renderItems}
scrollEventThrottle={16}
onEndReached={handleLoadMore}
onEndReachedThreshold={0}
/>
);
return (
<Tab.Navigator
screenOptions={({ route }) => screenOptions(route)}
keyboardDismissMode="auto"
>
<Tab.Screen name="Crypto" component={ScreenA} />
<Tab.Screen name="Forex" component={ScreenC} />
</Tab.Navigator>
);
};
export default Navigator;
OnEndReached is firing the handleLoadMore function and after the state change on data, the Flatlist is scrolling to the top.
1st reason
you have typo in "fetchMarketData", how exactly u get "newData" because i cant see it anywhere, maybe it should be "marketData" if not then u adding SAME old data PLUS undefined[...data, ...undefined]
2nd reason
reason why is that u call setPage(page + 1) and then "fetchMarketData" this is bad why ? because setState is async and it can be changed instant or after 5 secound, so u dont know when its changed and this is why we have hooks, you can use "useEffect" to handle this
change your "handleLoadMore" for example like this
const handleLoadMore = () => {
setPage(page + 1);
};
add useEffect hook that runs when "page" state changes
React.useEffect(() => {
(async() => {
setFetching(true)
const marketData = await getCryptoMarketData({ page });
if (marketData != "Network Error") {
setData([...data, ...marketData]);
} else {
Alert.alert(marketData, "Sorry for the inconvenience");
}
setFetching(false)
})()
}, [page])

Navigation Error when navigating to Home page from Firebase registration

I'm following this guide: https://www.freecodecamp.org/news/react-native-firebase-tutorial/ in attempt to learn how to use firebase, and even though I've followed the code very closely, I'm receiving a NAVIGATION error:
The action 'NAVIGATE' with payload {"name":"Home","params":{"user":{"id":"AWSKEmmUsua5koR1V3x5bapc3Eq2","email":"tk#gmail.com","fullName":"t"}}} was not handled by any navigator.
Do you have a screen named 'Home'?
I do however, have a screen named Home. App.js:
import Home from './src/Home';
import Login from './src/Login/Login';
import Registration from './src/Registration/Registration';
const Stack = createStackNavigator();
export default function App() {
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
return (
<NavigationContainer>
<Stack.Navigator>
{ user ? (
<Stack.Screen name="Home">
{props => <Home {...props} extraData={user} />}
</Stack.Screen>
) : (
<>
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Registration" component={Registration} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
When I use the Registration form to register a new user and Navigate to the Home page is when I get the error. Registration.js:
import { firebase } from '../firebase/config';
export default function Registration({ navigation }) {
const [fullName, setFullName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const onFooterLinkPress = () => {
navigation.navigate('Login');
}
const onRegisterPress = () => {
if (password !== confirmPassword) {
alert("Passwords do not match!");
return
}
// This works. However, navigation does not for some reason
firebase.auth()
.createUserWithEmailAndPassword(email, password)
.then((response) => {
const uid = response.user.uid
const data = {
id: uid,
email,
fullName
}
const usersRef = firebase.firestore().collection("users");
usersRef.doc(uid).set(data).then(() => {
// This is where the navigation error lies. It has nothing to do with the component
// This error happened even when I created a new plain Home component
navigation.navigate("Home", { user: data})
})
.catch((error) => alert(error))
})
.catch((error) => alert(error))
}
return (
....Input Forms
<TouchableOpacity
style={styles.loginButton}
onPress={() => onRegisterPress()}
>
<Text style={styles.buttonTitle}>Create Account</Text>
</TouchableOpacity>
I have used React Navigation before and haven't run into this issue. I am not using nested navigators and cannot see where the issue lies. Thank you for reading.
Adding to Göksel Pırnal answers:
At first, suppose there is no user. So We are in Registration Screen. At that stage, our navigator doesn’t even know whether there is any “Home” Screen. At this stage, our navigator only knows 2 screens: “Login” and “Registration” screens.
You need to notify our app.js whether anyone registered in the Registration screen or not. After that our app.js should change the value of 'user' in [user,setUser].
In your, App.js put this lines of code:
const [initializing,setInitializing]=useState(true)
useEffect(()=>{
const subscriber=firebase.auth().onAuthStateChanged((user)=>{
setUser(user)
setInitializing(false)
})
return subscriber
},[])
if (initializing) return null //Here you may use an Activity indicator
Then after rerendering our navigator will see the value of “user” has changed and it should navigate to the Home screen.
And guess what! You do not need to navigate manually from Registration Screen as you already put a condition in App.js ( in return () ).
You have a problem where you check the user value in App.js. After the registration is done, you did not assign the state in the App.js page and it will always be null. The Home page will not be added to the stack because the user value is null. That's why you got the error.
Solution: You need to notify App.js after registration.

Sending params with navigation.goBack in react navigation

hellow , how to send params navigation go back ?
const Onsubmit = (data, details = null) => {
console.log(details.formatted_address);
route.params.onPlaceChosen(
route.params.id,
details.formatted_address,
details.geometry
);
navigation.goBack();
};
here I want to pass the value from details.formatted_address to page B.
How to ?
If you are navigating from Screen A to Screen B, and when you want to go back to Screen A with running a callback in again Screen A, below is what you need to do:
In your Screen A (Source)
...
const onPlaceChosen = (params) => {
// here is your callback function
}
...
navigation.navigate('ScreenB', { onPlaceChosen })
...
In your Screen B (Destination)
..
const Onsubmit = (data, details = null) => {
navigation.state.params.onPlaceChosen(
route.params.id,
details.formatted_address,
details.geometry
);
navigation.goBack();
};
...
I did something like this based on https://reactnavigation.org/docs/5.x/hello-react-navigation/#passing-additional-props
const [params, setParams] = useState({});
<Stack.Navigator>
<Stack.Screen name="One">
{props => <FirstScreen {...props} paramsFromTwo={params} />}
</Stack.Screen>
<Stack.Screen name="Two">
{props => <SecondScreen {...props} onGoBack={(paramsFromSecond} => setParams(paramsFromSecond)} />}
</Stack.Screen>
</Stack.Navigator>
I did this way:
onPress={() => {
// Pass and merge params back to home screen
navigation.navigate({
name: 'Home',
params: { post: postText },
merge: true,
});
}}
It was extracted from:
https://reactnavigation.org/docs/params#passing-params-to-a-previous-screen
In my case, using navigation and route passed by props, the solution was:
route.params.onFilter({
route.params.id,
details.formatted_address,
details.geometry
});
navigation.goBack();

How to pass current state of App to Tab Navigation Screen

If I'm using React Navigation v5, what is the best way to pass the current state of a parent component (in my case, the main App) down through a Tab and Stack navigator to a screen that I'd like to use the current state in?
Following the documentation, I have created a stack navigator for each tab that holds the respective screens.
App.js contains a state that needs to be used for a few things. Most importantly, it will provide badge count on the Tab navigator, as well as be a source of Flatlist data on one of the tab screens.
What is the correct approach to getting the state from App all the way down to a child component in a stack navigator in a tab navigator?
App.js
const Tab = createBottomTabNavigator()
export default class App extends React.Component {
constructor(props){
super(props)
this.state = {
neededArray: []
}
}
const updateTheArray = (newArray) => {
this.setState({
neededArray: newArray
})
}
componentDidMount(){
//Listener that searches for nearby bluetooth beacons and updates the array with the passed function
startObserver(updateTheArray)
}
componentWillUnmount(){
stopObserver()
}
render(){
return(
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name = "Home"
component = { HomeStack }/>
<Tab.Screen
name = "About"
component = { AboutStack }/>
//The Stack that contains the screen that I need to use the App's state in
<Tab.Screen
name = "Nearby"
component = { NearbyStack }/>
</Tab.Navigator>
</NavigationContainer>
)
}
}
NearbyStack.js
//This stack holds the screen that I need to use the App's state in
const NearbyStackNav = createStackNav()
const NearbyStack = () => {
return(
<NearbyStackNav.Navigator>
<NearbyStackNav.Screen
name = "Nearby"
component = { NearbyScreen }
/>
</NearbyStackNav.Navigator>
)
}
NearbyScreen.js
//The screen that I want to use the App's state in
const NearbyScreen = () => {
return(
<View>
<FlatList
//Where I would like to use the App's state
/>
</View>
)
}
You can pass some initial params to a screen. If you didn't specify any params when navigating to this screen, the initial params will be used. They are also shallow merged with any params that you pass. Initial params can be specified with an initialParams prop:
Usage
<Tab.Screen
name = "Nearby"
component = { NearbyStack }
initialParams={{ arrayItem: this.state.neededArray }}
/>
NearbyScreen.js
React.useEffect(() => {
if (route.params?.arrayItem) {
// Post updated, do something with `route.params.arrayItem`
// For example, send the arrayItem to the server
}
}, [route.params?.arrayItem]);
My solution was to use React's Context API.
BeaconContext.js - New
import React from 'react'
const BeaconContext = React.createContext()
export default BeaconContext
App.js - Modified
import BeaconContext from './path/to/BeaconContext'
const Tab = createBottomTabNavigator()
export default class App extends React.Component {
constructor(props){
super(props)
this.state = {
neededArray: []
}
}
const updateTheArray = (newArray) => {
this.setState({
neededArray: newArray
})
}
componentDidMount(){
startObserver(updateTheArray)
}
componentWillUnmount(){
stopObserver()
}
render(){
return(
// Wrap the nav container in the newly created context!!!
<BeaconContext.Provider value = { this.state.neededArray }
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name = "Home"
component = { HomeStack }/>
<Tab.Screen
name = "About"
component = { AboutStack }/>
<Tab.Screen
name = "Nearby"
component = { NearbyStack }/>
</Tab.Navigator>
</NavigationContainer>
</BeaconContext.Provider>
)
}
}
NearbyStack.js - Unchanged
const NearbyStackNav = createStackNav()
const NearbyStack = () => {
return(
<NearbyStackNav.Navigator>
<NearbyStackNav.Screen
name = "Nearby"
component = { NearbyScreen }
/>
</NearbyStackNav.Navigator>
)
}
NearbyScreen.js - Modified
import BeaconContext from './path/to/BeaconContext'
const NearbyScreen = () => {
return(
<View>
//Wrap the component in the new context's consumer!!!
<BeaconContext.Consumer>
{
context => <Text>{ context }</Text>
}
</BeaconContext.Consumer>
</View>
)
}
I've been struggling with the exact same issue - when using the initialProps property to pass a state to a Tab.Screen the screen never receives any updates. It reads the intial state value once then nothing.
To make it work I skipped using the initialProps property and instead used the children property on Tab.Screen like so:
App containing <Tab.Navigator> and <Tab.Screen>:
const[myBool, setMyBool] = useState(false)
<Tab.Screen
name="MyTab"
children={() => (
<MySecondScreen passedStateParam={ myBool } />
)}
.
.
.
</Tab.Screen>
MySecondScreen consuming updates on passed myBool state:
export function MySecondScreen ({ passedStateParam }) {
const myPassedBoolState = passedStateParam
React.useEffect(() => {
if(myPassedBoolState) {
//Act upon App.tsx updating the state
}
}, [myPassedBoolState])
}
Not sure if I'm missing something when trying to perform this with the initialParams property but this way (using children property) I got it to work at least.