React-Native navigate after a notification press - javascript

Me and a co-worker have been working on a React-Native app for quite some time now.
We've been struggling with this problem for a few days now and we don't seem to find a suitable solution.
We are using a few dependencies to make this work.
notifee (https://notifee.app/)
React-native Firebase (https://rnfirebase.io/)
React-navigation (https://reactnavigation.org/)
We've tried using the useNavigation() in the notifee.onBackgroundEvent() but we keep getting an Invalid hook call. When we try to pass the navigation as a property trough the function it returns undefined.
We want to navigate the user to a page after the user has pressed the notification when the app is closed/killed
index.js
import {AppRegistry} from 'react-native';
import React, {useEffect, useState} from 'react';
import messaging from '#react-native-firebase/messaging';
import App from './App';
import {name as appName} from './app.json';
import getNotification from './src/services/RESTnotification';
import notifee, {EventType} from "#notifee/react-native";
import {useNavigation} from "#react-navigation/native";
messaging().setBackgroundMessageHandler(async remoteMessage => {
await getNotification();
});
notifee.onBackgroundEvent(async ({type, detail}) => {
const {notification, pressAction} = detail;
if (type === EventType.PRESS) {
console.log('User pressed an action with the id: ', pressAction.id);
// navigate here
}
await notifee.cancelNotification(notification.id);
console.log('background-event');
});
AppRegistry.registerComponent(appName, () => App);
RESTnotification.js
import messaging from '#react-native-firebase/messaging';
import notifee, {AndroidImportance, EventType} from "#notifee/react-native";
import AsyncStorage from "#react-native-community/async-storage";
import React from "react";
async function requestUserPermission() {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Firebase authorization:', authStatus);
}
}
export const checkToken = async () => {
const fcmToken = await messaging().getToken();
if (fcmToken) {
console.log(fcmToken);
}
await AsyncStorage.setItem("fcmToken", fcmToken);
}
export default async function getNotification() {
const channelId = await notifee.createChannel({
id: 'important',
name: 'Important Notifications',
importance: AndroidImportance.HIGH,
});
await notifee.displayNotification({
title: '<p><b>Verandering bij proces: {naam}</span></p></b></p>',
body:
'{user} heeft het proces {procesnaam} afgekeurd',
data: {
processId: '12345678'
},
android: {
channelId: 'important',
importance: AndroidImportance.HIGH,
smallIcon: "ic_notification",
//largeIcon: require('../../assets/images/apple-touch-icon.png'),
pressAction: {
id: 'default',
launchActivity: 'default'
},
},
});
}
checkToken();
requestUserPermission();
Are we overlooking something?

At the startup you can't use the hook useNavigation as it's still not built.
For this kind of use case, you need to use a different approach, the deep linking: https://reactnavigation.org/docs/deep-linking.
The basic flow is that you need to add a path on your notification data (for example myapp://user/profile) and move your notification handler inside the Deep Linking configuration.
For a practical example you can check this tutorial: https://medium.com/cybermonkey/deep-linking-push-notifications-with-react-navigation-5fce260ccca2

Related

React Native and FIrebase: Making an async call seems to run through twice

as I continue to work on my issues I've got one that's kind of perplexing. my app has two JS files that interact with each other, but what's odd is it seems that then I call the function to get the data from firestore it seems that it wants to run it twice.
Here is the HomeScreen.js file that is suppoed to call UserInformation
import { Pressable, Text } from 'react-native';
import { useEffect, useId, useState } from 'react';
import { async } from '#firebase/util';
import { FlatList } from 'react-native-gesture-handler';
import {getStorage, ref, getDownloadURL} from 'firebase/storage';
//import the user infomration component
import UserInformation from '../components/UserInformation';
import ClubInformation from '../components/ClubInformation';
import AircraftInformation from '../components/AircraftInformation';
function HomeScreen() {
const userDetails = UserInformation();
const clubDetails = ClubInformation();
const aircraftDetails = AircraftInformation();
if (userDetails !== null) {
//console.log(userDetails)
//console.log(clubDetails)
//console.log(aircraftDetails)
}
if(!userDetails) {
return <Text>Loading...</Text>
}
if(!clubDetails) {
return <Text>Loading...</Text>
}
return <Text>Welcome {userDetails.first} {userDetails.last} {clubDetails.name}! This is a placeholder for future updates in the home screen</Text>
}
export default HomeScreen
the userDetails calls is below:
userInformation.js
import { useState, useEffect } from "react";
import { getAuth } from "firebase/auth";
import { doc, getDoc } from "firebase/firestore";
import { db } from '../components/FirebaseConfig';
export default function UserInformation() {
const [userDetails, setUserDetails] = useState(null);
useEffect(() => {
async function getUserDetails() {
const user = getAuth().currentUser;
const userRef = doc(db, "users", user.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
const data = docSnap.data();
setUserDetails(data);
} else {
console.log("No such User!");
}
}
getUserDetails();
}, []);
return userDetails;
}
Whenever I do a console log from the HomeScreen it seems to first return null, then it returns the data. Is there something I'm doing wrong here (I'm sure it is) or is it standard behavior? Thank you in advance
I tried to run this multiple ways but those other functions that i have that also query the db seem to do the same thing. I don't know if it's something up with my async
This is expected behavior when UserInformation function is working as a custom hook.
The first time when HomeScreen component was initialized/mounted, the default value userDetails is null.
After a few seconds, the app fetches data from Firestore and updates userDetails state value which trigger a second rerender.
Note: Recommended best practice is always prefixed custom hooks function name use[FUNCTION_NAME] like useUserInformation.
Your logic is completely wrong. You couldn't wait on referencing hook function.
Even you can use useEffect hook if you want to get valid response.
function HomeScreen() {
const userDetails = UserInformation();
const clubDetails = ClubInformation();
const aircraftDetails = AircraftInformation();
useEffect(()=>{
if (userDetails) {
console.log(userDetails)
}
}, [userDetails])
if(!userDetails) {
return <Text>Loading...</Text>
}
if(!clubDetails) {
return <Text>Loading...</Text>
}
return <Text>Welcome {userDetails.first} {userDetails.last} {clubDetails.name}! This is a placeholder for future updates in the home screen</Text>
}
As I said in my comment you don't need to create your custom hook functions. It's enough with normal functions it will work like this.
async function getUserDetails() {
const user = getAuth().currentUser;
const userRef = doc(db, "users", user.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
const data = docSnap.data();
return data;
} else {
console.log("No such User!");
return null
}
}
function HomeScreen() {
const [userDetails, setUserDetails] = useState(null)
useEffect(()=>{
getUserDetails().then(data=>{
if (data != null) {
setUserDetails(data)
}
})
}, [])
if (!userDetails) {
return <Text>Loading...</Text>
}
return <Text>Welcome {userDetails.first} {userDetails.last}</Text>
}

(pinia, vuex) Why can't I access states and getters in main.js?

Solved
Although not exactly my solution to this problem, the answer given below from #Po Wen Chen was helpful but it doesn't work exactly as I want it to. data in the form of proxy continues to come, this is not important, the conditions are met.
The main problem was that every time the page was refreshed, the states were null, so their values were flying. After we searched, my states became permanent with the library named pinia-plugin-persistedstate.
Solved
I did as it is written in the documentation, but instead of receiving a user data, I am getting a proxy type data.
Documentation of pinia describing how to use store outside of component
The account store performs the registration and login of the users, that is, the auth processes.
import axios from "axios";
import { defineStore } from "pinia";
const useAccountStore = defineStore("account", {
state: () => ({
user: null,
returnUrl: null
}),
getters: {
isLoggedIn: state => (state.user ? true : false),
getUser: state => state.user,
},
actions: {
async init() {
console.log('run the init')
this.fetchUser()
},
async registerUser(user) {
await axios.post("/account/register", {user})
},
async login(credentials) {
const user = await axios.post("/account/session", credentials)
this.user = user.data
},
async logout() {
await axios.delete("/account/session")
this.user = null
},
async fetchUser() {
const user = await axios.get("/account")
this.user = user.data
},
},
})
export { useAccountStore };
In main.js
import antd from "ant-design-vue"
import "ant-design-vue/dist/antd.css"
import axios from "axios"
import { createPinia } from "pinia"
import { createApp } from "vue"
import App from "./app.vue"
import { router } from "./router"
import { useAccountStore } from "./store/account.store"
// import './assets/main.css'
axios.defaults.baseURL = import.meta.env.VITE_API_URL
axios.defaults.withCredentials = true
createApp(App)
.use(createPinia())
.use(router)
.use(antd)
.mount("#app")
useAccountStore().init()
router.beforeEach(async (to) => {
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login', '/register'];
const authRequired = !publicPages.includes(to.path);
const authStore = useAccountStore();
console.log('authStore.isLoggedIn', authStore)
if (authRequired && !authStore.user) {
authStore.returnUrl = to.fullPath;
return '/login';
}
});
I press the authStore to console and it comes back to me as a proxy.
return the proxy
Although not exactly my solution to this problem, the answer given below from #Po Wen Chen was helpful but it doesn't work exactly as I want it to. data in the form of proxy continues to come, this is not important, the conditions are met.
The main problem was that every time the page was refreshed, the states were null, so their values were flying. After we searched, my states became permanent with the library named pinia-plugin-persistedstate.

Email verification with Next js and express

I am building a web application in which i need to verify the user's email sent via the client side (React.js and Next.js) and i'm following this youtube tutorial. However, the mentor is using create-react-app CLI and React-Router-Dom for the routing system which doesn't really go with my current needs.
Moreover, I found this method online using HOC :
import React from 'react';
import Router from 'next/router';
const login = '/register?redirected=true'; // Define your login route address.
const checkUserAuthentication = () => {
return { auth: null }; // change null to { isAdmin: true } for test it.
};
export default WrappedComponent => {
const hocComponent = ({ ...props }) => <WrappedComponent {...props} />;
hocComponent.getInitialProps = async (context) => {
const userAuth = await checkUserAuthentication();
// Are you an authorized user or not?
if (!userAuth?.auth) {
// Handle server-side and client-side rendering.
if (context.res) {
context.res?.writeHead(302, {
Location: login,
});
context.res?.end();
} else {
Router.replace(login);
}
} else if (WrappedComponent.getInitialProps) {
const wrappedProps = await WrappedComponent.getInitialProps({...context, auth: userAuth});
return { ...wrappedProps, userAuth };
}
return { userAuth };
};
return hocComponent;
};
The code above helps me to have a private route that the user cannot access unless he's authenticated (currently no programming included), but on the other hand i still need a page in the following route :
'pages/user/activate/[token].js' // the link sent via email from express back end.
What i need now is to create this page using Next routing system in order to get the token and decode it to move forward with the back end and save the user into MongoDB, and in order to accomplish that, i have created my [token].js page with the following code :
import React, {useState, useEffect} from 'react'
import { ToastContainer, toast } from 'react-toastify';
import axios from 'axios';
import jwt from 'jsonwebtoken';
import { authenticate, isAuth } from '../helpers/auth';
import { Link, Redirect } from 'react-router-dom';
const Activate = ({ match }) => {
const [formData, setFormData] = useState({
email: '',
token: '',
show: true
});
const { email, token, show } = formData;
useEffect(() => {
let token = match.params.token;
let { email } = jwt.decode(token);
if (token) {
setFormData({ ...formData, email, token });
}
console.log(token, email);
}, [match.params.token]);
return (
<>
{isAuth() ? <Redirect to="/" /> : null}
<p>Account activated, please log in</p>
</>
)
};
export default Activate;
However, i keep getting this error :
TypeError: Cannot read property 'params' of undefined
at Activate (C:\Users\Hp\Desktop\SMP\client\.next\server\pages\user\activate\[token].js:245:13)
at processChild (C:\Users\Hp\Desktop\SMP\client\node_modules\react-dom\cjs\react-dom-
server.node.development.js:3353:14)
at resolve (C:\Users\Hp\Desktop\SMP\client\node_modules\react-dom\cjs\react-dom-
server.node.development.js:3270:5)
at ReactDOMServerRenderer.render (C:\Users\Hp\Desktop\SMP\client\node_modules\react-dom\cjs\react-
dom-server.node.development.js:3753:22)
at ReactDOMServerRenderer.read (C:\Users\Hp\Desktop\SMP\client\node_modules\react-dom\cjs\react-dom-
server.node.development.js:3690:29)
at renderToString (C:\Users\Hp\Desktop\SMP\client\node_modules\react-dom\cjs\react-dom-
server.node.development.js:4298:27)
at Object.renderPage (C:\Users\Hp\Desktop\SMP\client\node_modules\next\dist\next-
server\server\render.js:53:851)
at Function.getInitialProps (C:\Users\Hp\Desktop\SMP\client\.next\server\pages\_document.js:293:19)
at loadGetInitialProps (C:\Users\Hp\Desktop\SMP\client\node_modules\next\dist\next-
server\lib\utils.js:5:101)
at renderToHTML (C:\Users\Hp\Desktop\SMP\client\node_modules\next\dist\next-
server\server\render.js:53:1142)
I couldn't find a solution because i believe that i'm doing something wrong whether in my code or in the logic implemented.
Is there any way that i can do this properly ?
Thank you in advance !

Line 105:14: 'accountSelector' is not defined no-undef

Could someone please explain what is going wrong in simple terms so I know how to fix this and can deal with it next time I encounter it.
I have looked through all related questions I could find on stackoverflow and haven't been able to fix it, if I have missed one that answers this then please link it.
I have had this error in the past but usually that was just because I had a typo (e.g. a capital instead of a lowercase) or did not import something correctly however that is not the case this time as far as I can tell.
FIRST CODE app.js
SECOND CODE interactions.js
Here is my code
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Navbar from './Navbar'
import Web3 from 'web3';
import { connect } from 'react-redux'
// import Token from '../abis/Token.json'
import {
loadWeb3,
loadAccount,
loadToken,
loadExchange
} from '../store/interactions'
class App extends Component {
componentWillMount() {
this.loadBlockchainData(this.props.dispatch)
}
async loadBlockchainData(dispatch) {
const web3 = loadWeb3(dispatch)
const network = await web3.eth.net.getNetworkType()
const networkId = await web3.eth.net.getId()
const accounts = await loadAccount(web3, dispatch) // <<--
const token = loadToken(web3, networkId, dispatch)
loadExchange(web3, networkId, dispatch)
}
// ......................
function mapStateToProps(state) {
return {
account: accountSelector(state)
}
}
export default connect(mapStateToProps)(App);
import Web3 from 'web3'
import {
web3Loaded,
web3AccountLoaded,
tokenLoaded,
exchangeLoaded
} from './actions'
import Token from '../abis/Token.json'
import Exchange from '../abis/Exchange.json'
export const loadWeb3 = (dispatch) => {
const web3 = new Web3(Web3.givenProvider || 'http://localhost:7545')
dispatch(web3Loaded(web3))
return web3
}
export const loadAccount = async (web3, dispatch) => {
const accounts = await web3.eth.getAccounts()
const account = accounts[0]
dispatch(web3AccountLoaded(account))
return account
}
export const loadToken = async (web3, networkId, dispatch) => {
try {
const token = new web3.eth.Contract(Token.abi, Token.networks[networkId].address) // new 이거 의존성(버전) 문제 이거 조심!!!!!
dispatch(tokenLoaded(token))
return token
} catch (error) {
window.alert('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
export const loadExchange = async (web3, networkId, dispatch) => {
try {
const exchange = new web3.eth.Contract(Exchange.abi, Exchange.networks[networkId].address)
dispatch(exchangeLoaded(exchange))
return exchange
} catch (error) {
window.alert('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
i don'k now why this happening to me
but please let me know this problem if you know this issue
The problem seems to be that you do not define or import the accountSelector function anywhere.
You usually define Redux selector functions in your reducer definition files: they take the current Redux store state as argument (and optionally the connected component props) and return the value to be used in your MapStateToProps object property.
Ex.
export const accountSelector = (state) => state.account
You can read more about selectors on the dedicated Redux resources page
replace this
function mapStateToProps(state) {
return {
account: accountSelector(state)
}
}
with this
function mapStateToProps(state) {
return {
account: state.accountSelector
}
}
you are passing complete state in variable instead of accessing.
for your reference, how to access please go through official documentation for your better understanding Redux

Connection to Facebook with React and Firebase

i'm french, sorry for my little english.
I've a problem with Reactjs and Firebase, an error when i want connect with Facebook. I look tutorial in Udemy platform. This is a video for learn React
REBASE: The Firebase endpoint you are trying to listen to must be a string. Instead, got undefined
Parts of code Admin.js :
import React, { Component } from 'react'
import AjouterRecette from './AjouterRecette'
import AdminForm from './AdminForm'
import Login from './Login'
import firebase from 'firebase/app'
import 'firebase/auth'
import base, { firebaseApp } from '../base'
class Admin extends Component {
state = {
uid: null,
chef: null
}
componentDidMount () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.handleAuth({ user })
}
})
}
handleAuth = async authData => {
console.log(authData)
const box = await base.fetch(this.props.pseudo, { context: this })
if (!box.chef) {
await base.post(`${this.props.pseudo}/chef`, {
data: authData.user.uid
})
}
this.setState({
uid: authData.user.uid,
chef: box.chef || authData.user.uid
})
}
authenticate = () => {
const authProvider = new firebase.auth.FacebookAuthProvider()
firebaseApp
.auth()
.signInWithPopup(authProvider)
.then(this.handleAuth)
}
...
export default Admin
Thank's
Have a good day.
......................................................................................................................................................................................................................................................................................................................................................................................................
I've got exactly the same problem, probably because I follow the same training as you.
Your error is here :
const box = await base.fetch(this.props.pseudo, { context: this })
because this.props.pseudo is null.
in app.js, in the admin component, write
pseudo={this.props.match.params.pseudo}
and not
pseudo={this.state.pseudo}
and that shoudl work.
regards

Categories