Conditionally choosing screens for auth flow in react navigation v5 not updating - firebase

I'm trying to create my auth flow for a react native application using react navigation v5. Even though I am sure that firebase.auth().currentUser != null is evaluating to true when I sign in and false when I sign out, the page will not change away automatically to the "Closet" page like it says here: https://reactnavigation.org/docs/auth-flow/#how-it-will-work . I'm certain that I'm signing in correctly because I can print out firebase.auth().currentUser and see if it is null or not.
Why is it not automatically changing pages when I sign in and out? My code is below
Note: I was using isLoggedIn in the conditional instead of firebase.auth().currentUser but neither are working
import React from 'react';
import {createStackNavigator} from '#react-navigation/stack'
import SignIn from '../screens/SignIn';
import TabNavigator from './TabNavigator';
import { connect } from 'react-redux';
import firebase from '../config/fbConfig';
const Stack = createStackNavigator();
const AuthNavigator = () => {
var isLoggedIn = false;
firebase.auth().onAuthStateChanged(function(user) {
if(user){
console.log("Auth says logged in");
isLoggedIn = true;
} else {
console.log("Auth says logged out");
isLoggedIn = false;
}
})
return (
<Stack.Navigator headerMode="none">
{firebase.auth().currentUser != null ? (
<Stack.Screen name="Closet" component={TabNavigator} />
) : (
<Stack.Screen name="Sign In" component={SignIn} />
)}
</Stack.Navigator>
);
}
export default AuthNavigator;

Its because I wasn't using state. I replaced the declaration of isLoggedIn with
const [isLoggedIn, setIsLoggedIn] = useState(false);

Related

How to use useState in root layout/page in app directory of nextjs 13

In Nextjs 13 - experimental app directory, if I wanted to use useState on the root layout/page I must add ‘use client’ to the code, which effectively prevents all nested components from being server components.. how can I work around this so that I can use useState and still have server components. Thanks to any responders.
I don't know if this answers to your question (it's better to add some example code to help users understand your problem)
If you create a Server Component, and in that component you add your Client Component, it works fine. For example
ClientComponent.tsx
"use client";
import {useState} from 'react';
export default function ClientComponent() {
const [count, setCount] = useState(0);
return (
<>
<h1>Client Component</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
)
}
ServerComponent.tsx
async function getData(){
const res = await fetch('http://localhost:3000/api/hello');
return await res.json();
}
export default async function ServerComponent() {
const data = await getData()
return (
<>
<h1>Server Component</h1>
<p>{data.name}</p>
</>
)
}
Api hello.ts
export default async function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
Your page
import ClientComponent from "./ClientComponent";
import ServerComponent from "./ServerComponent";
export default function Page() {
return(<>
<ClientComponent/>
<ServerComponent/>
</>
)
}
In this example ServerComponent is rendered on the server, but ClientComponent on the client so it maintain interactivity
Hope this will help

Best way to capture screen time and press events using React Native Expo and Firebase Analytics

My group and I are currently working on a mobile app using expo-cli and firebase as the backend. One of the requirements is we need to get users' screen time and record how frequently users press certain buttons. According to expo firebase documentation, it only supports limited Firebase Analysis. We were wondering what would be the best way to use Firebase Analytics with Expo to capture screen time and button pressed frequencies.
Screen Tracking
Screen tracking in React Native is different than in a native app since some navigation libraries run inside one Activity/Viewcontroller.
Assuming you are using react-native-navigation, which does have full native navigation support you can handle screen tracking like this.
import analytics from '#react-native-firebase/analytics';
import { Navigation } from 'react-native-navigation';
Navigation.events().registerComponentDidAppearListener(async ({ componentName, componentType }) => {
if (componentType === 'Component') {
await analytics().logScreenView({
screen_name: componentName,
screen_class: componentName,
});
}
});
Look here for the documentation
If you are using react-navigation you can still work around the lack of native screen support by hooking into the events that are provided.
import analytics from '#react-native-firebase/analytics';
import { NavigationContainer } from '#react-navigation/native';
const App = () => {
const routeNameRef = React.useRef();
const navigationRef = React.useRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
routeNameRef.current = navigationRef.current.getCurrentRoute().name;
}}
onStateChange={async () => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef.current.getCurrentRoute().name;
if (previousRouteName !== currentRouteName) {
await analytics().logScreenView({
screen_name: currentRouteName,
screen_class: currentRouteName,
});
}
routeNameRef.current = currentRouteName;
}}
>
...
</NavigationContainer>
);
};
export default App;
Here you can find a full example starter app.
Button Press Events
For logging press events there's a lot of documentation on the RNFirebase website.
A simple example to track a custom event that could be an onPress or anything would look like this:
import react, { useEffect } from 'react';
import { View, Button } from 'react-native';
import analytics from '#react-native-firebase/analytics';
function App() {
return (
<View>
<Button
title="Add To Basket"
onPress={async () =>
await analytics().logEvent('onPressAddToBasket', {
id: 3745092,
item: 'Your product name',
description: ['round neck', 'long sleeved'],
size: 'L',
wheneverYouWantToTrack: true,
})
}
/>
</View>
);
}

Next.js: How to clear browser history with Next Router?

I created a wrapper for the pages which will bounce unauthenticated users to the login page.
PrivateRoute Wrapper:
import { useRouter } from 'next/router'
import { useUser } from '../../lib/hooks'
import Login from '../../pages/login'
const withAuth = Component => {
const Auth = (props) => {
const { user } = useUser();
const router = useRouter();
if (user === null && typeof window !== 'undefined') {
return (
<Login />
);
}
return (
<Component {...props} />
);
};
if (Component.getInitialProps) {
Auth.getInitialProps = Component.getInitialProps;
}
return Auth;
};
export default withAuth;
That works \o/, However I noticed a behavior when I log out, using Router.push('/',), to return the user to the homepage the back button contains the state of previous routes, I want the state to reset, as a user who is not authenticated should have an experience as if they're starting from scratch...
Thank you in advance!
You can always use Router.replace('/any-route') and the user will not be able to go back with back button

Next JS fetch data once to display on all pages

This page is the most relevant information I can find but it isn't enough.
I have a generic component that displays an appbar for my site. This appbar displays a user avatar that comes from a separate API which I store in the users session. My problem is that anytime I change pages through next/link the avatar disappears unless I implement getServerSideProps on every single page of my application to access the session which seems wasteful.
I have found that I can implement getInitialProps in _app.js like so to gather information
MyApp.getInitialProps = async ({ Component, ctx }) => {
await applySession(ctx.req, ctx.res);
if(!ctx.req.session.hasOwnProperty('user')) {
return {
user: {
avatar: null,
username: null
}
}
}
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return {
user: {
avatar: `https://cdn.discordapp.com/avatars/${ctx.req.session.user.id}/${ctx.req.session.user.avatar}`,
username: ctx.req.session.user.username
},
pageProps
}
}
I think what's happening is this is being called client side on page changes where the session of course doesn't exist which results in nothing being sent to props and the avatar not being displayed. I thought that maybe I could solve this with local storage if I can differentiate when this is being called on the server or client side but I want to know if there are more elegant solutions.
I managed to solve this by creating a state in my _app.js and then setting the state in a useEffect like this
function MyApp({ Component, pageProps, user }) {
const [userInfo, setUserInfo] = React.useState({});
React.useEffect(() => {
if(user.avatar) {
setUserInfo(user);
}
});
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<NavDrawer user={userInfo} />
<Component {...pageProps} />
</ThemeProvider>
);
}
Now the user variable is only set once and it's sent to my NavDrawer bar on page changes as well.
My solution for this using getServerSideProps() in _app.tsx:
// _app.tsx:
export type AppContextType = {
navigation: NavigationParentCollection
}
export const AppContext = createContext<AppContextType>(null)
function App({ Component, pageProps, navigation }) {
const appData = { navigation }
return (
<>
<AppContext.Provider value={appData}>
<Layout>
<Component {...pageProps} />
</Layout>
</AppContext.Provider>
</>
)
}
App.getInitialProps = async function () {
// Fetch the data and pass it into the App
return {
navigation: await getNavigation()
}
}
export default App
Then anywhere inside the app:
const { navigation } = useContext(AppContext)
To learn more about useContext check out the React docs here.

How to test store state changes for redux connected HOC component using react-test-renderer?

I am trying to test the follow component, which is based on a pretty common "ProtectedRoute" HOC/wrapper example on several React training sites. The basic concept is to wrap protected components at the <Route> level in order to redirect unauthenticated users.
//Authenticated.js
import React from 'react';
import PropTypes from 'prop-types';
import { history } from 'store';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
export default function (Component) {
class Authenticated extends React.Component {
constructor(props){
super(props);
this.state = {
authenticated: props.authenticated
}
}
static getDerivedStateFromProps(props) {
return {
authenticated: props.authenticated
}
}
componentDidMount() {
this._checkAndRedirect();
}
componentDidUpdate() {
this._checkAndRedirect();
}
_checkAndRedirect() {
const { authenticated, redirect } = this.props;
if (!authenticated) {
redirect();
}
}
render() {
return (
<div>
{ this.props.authenticated ? <Component {...this.props} /> : null }
</div>
);
}
}
const mapStoreStateToProps = (store) => {
return {
authenticated: store.auth.authenticated
};
};
const mapDispatchToProps = dispatch => bindActionCreators({
redirect: () => history.push('/')
}, dispatch)
Authenticated.propTypes = {
authenticated: PropTypes.bool,
redirect: PropTypes.func.isRequired
}
return connect(
mapStoreStateToProps,
mapDispatchToProps
)(Authenticated);
}
So far, I've written the following tests using react-test-renderer and redux-mock-store:
//Authenticated.spec.js
import React from 'react';
import { Provider } from 'react-redux';
import { Router, Route } from 'react-router-dom';
import { createMemoryHistory } from "history";
import renderer from 'react-test-renderer';
import mockStore from 'tests/mockStore';
import Authenticated from 'components/common/Authenticated';
import Home from 'components/Home';
describe('Authenticated', () => {
it('displays protected component if authenticated', () => {
const AuthenticatedComponent = Authenticated(Home);
const store = mockStore({ auth: { authenticated: true } });
store.dispatch = jest.fn();
const history = createMemoryHistory();
history.push('/admin')
const component = renderer.create(
<Provider store={store}>
<Router history={history} >
<AuthenticatedComponent />
</Router>
</Provider>
);
expect(component.toJSON()).toMatchSnapshot();
expect(history.location.pathname).toBe("/admin");
});
it('redirects to login if not authenticated', () => {
const AuthenticatedComponent = Authenticated(Home);
const store = mockStore({ auth: { authenticated: false } });
store.dispatch = jest.fn();
const history = createMemoryHistory();
const component = renderer.create(
<Provider store={store}>
<Router history={history} >
<AuthenticatedComponent />
</Router>
</Provider>
);
expect(component.toJSON()).toMatchSnapshot();
expect(history.location.pathname).toBe("/");
});
});
Both of these tests run fine, and pass as expected, however I am now interested in testing following real world scenario:
A user is currently logged in, with authenticated=true in the store. After some time their cookie expires. For our example, let's say we periodically check the state of auth with server, and it triggers some action to fire, causing the store's new state to result in authenticated=false.
Unless I just never came across an example, I am fairly certain redux-mock-store (even though it's literally named "mock-store") does not actually call any reducers to return a new "mock store" state when you call store.dispatch(myAction()). I am able to test the state changes in myAction.spec.js, but I am still unable to simulate the HOC receiving the updated state/prop in getDerivedStateFromProps
From what I can tell the tests I've written cover the two scenarios of where the component loads with authenticated=true/false initial states and that works as expected, but when I look at the coverage results, the getDerivedStateFromProps function is not being covered by these tests.
How would I go about writing a test to simulate the change of authentication boolean in the store state and asserting that the Authenticated component received the change in getDerivedStateFromProps (previously componentWillReceiveProps) in order to make sure I am covering this in testing?

Resources