Steps for preloading a user prior to the page loading

Main Concern

I currently have an Auth Provider set up in my application that wraps around the entire _app.tsx file. This allows me to utilize the "useAuth" hook and access the user object from any part of the app. However, I am facing an issue when using this hook to conditionally load the Navbar component. During the initial few seconds of page load, the user object is not yet available, causing a brief flash of the logged out component on the screen before the correct component is loaded. I have researched various methods such as GetInitialProps and GetServerSideProps to address this problem, but I'm unsure about the most effective solution.

[Update] - Additional Question

  • How can I ensure that a Firebase user is fully loaded before a component renders in Next.js?

Technology Stack Utilized

  • Next.js
  • Firebase Authentication
  • MongoDB

Visual Representation

View the gif demonstrating the flashing Navbar

Code Snippets

_app.tsx

function MyApp({ Component, pageProps }: AppProps) {
    return (
        <>
            <Global
                styles={css`
                    button {
                        border: none;
                    }
                    input {
                        border: none;
                    }
                `}
            />
            <ThemeProvider theme={theme}>
                <CSSReset />
                <AuthProvider>
                    <Page>
                        <Navbar />
                        <Component {...pageProps} />

                        <Footer />
                    </Page>
                </AuthProvider>
            </ThemeProvider>
        </>
    );
}

export default MyApp;

AuthContext.tsx

import React, { useState, useEffect, useContext, createContext } from 'react';
import nookies from 'nookies';
import { firebase } from './initFirebase';
import initFirebase from './initFirebase';
import { auth } from 'firebase';

const AuthContext = createContext<{ user: firebase.User | null }>({
    user: null,
});

export function AuthProvider({ children }: any) {
    const [user, setUser] = useState<firebase.User | null>(null);

    useEffect(() => {
        return firebase.auth().onIdTokenChanged(async (user) => {
            if (!user) {
                setUser(null);
                nookies.set(undefined, 'token', '', {});
                return;
            }

            const token = await user.getIdToken();
            setUser(user);
            setLoading(false);
            nookies.set(undefined, 'token', token, {});
        });
    }, []);

    return (
        <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
    );
}

// signOut function
export const signOut = async () => {
    await auth().signOut();
};

// useAuth hook
export const useAuth = () => {
    return useContext(AuthContext);
};

Navbar component

const Navbar = () => {
    const { user} = useAuth();
    const [loggedOut, setLoggedOut] = useState(false);
    const toast = useToast();
    const router = useRouter();
    const { isOpen, onOpen, onClose } = useDisclosure();

    const handleSignOut = () => {
        signOut()
            .then((result) => {
                toast({
                    title: 'Signed Out',
                    description: 'You have successfully signed out of your account.',
                    status: 'success',
                    duration: 4000,
                    isClosable: true,
                    position: 'top',
                });
                setLoggedOut(true);
                router.push('/');
            })
            .catch((error) => {
                var errorCode = error.code;
                var errorMessage = error.message;
                toast({
                    title: errorCode,
                    description: errorMessage,
                    status: 'error',
                    duration: 4000,
                    isClosable: true,
                    position: 'top',
                });
            });
    };

    return (
        <>
            <NavbarWrapper>
                <NavbarElementWrapper>
                    <LogoLink>
                        <Link href='/'>
                            <Logo />
                        </Link>
                    </LogoLink>
                </NavbarElementWrapper>

                {user ? (
                    <SplitLinks>
                        <NavbarElementWrapper>
                            <TimelineModal />
                        </NavbarElementWrapper>

                        <Menu>
                            <Tooltip label='Account Details' aria-label='account details'>
                                <MenuButton style={{ outline: 'none' }}>
                                    <Avatar
                                        src={user.photoURL}
                                        name={user.displayName || user.email.split('@')[0]}
                                    />
                                </MenuButton>
                            </Tooltip>
                            <MenuList>
                                <MenuGroup>
                                    <MenuItem onClick={() => onOpen()} height='100%'>
                                        <Box as={FaUser} mr='12px' />
                                        Account
                                    </MenuItem>
                                    <MenuItem onClick={() => router.push('/timelines')} height='100%'>
                                        <Box as={MdTimeline} mr='12px' />
                                        Timelines
                                    </MenuItem>
                                    <MenuDivider />
                                    <MenuItem justifyContent='center' style={{ background: 'none' }}>
                                        <Button
                                            onClick={handleSignOut}
                                            leftIcon='arrow-forward'
                                            variantColor='red'
                                        >
                                            Sign Out
                                        </Button>
                                    </MenuItem>
                                </MenuGroup>
                            </MenuList>
                        </Menu>
                    </SplitLinks>
                ) : (
                    <SplitLinks>
                        <LinkHoverWrapper first={true}>
                            <LoginModal />
                        </LinkHoverWrapper>
                        <LinkHoverWrapper>
                            <RegistrationModal />
                        </LinkHoverWrapper>
                    </SplitLinks>
                )}

                <AccountModal isOpen={isOpen} onClose={onClose} />
            </NavbarWrapper>
        </>
    );
};

export default Navbar;

Answer №1

When dealing with authenticated pages, a practical approach is to introduce a temporary skeleton structure during the authentication process. Similar to how YouTube handles it, consider displaying a placeholder until the isLoading status changes from true to false. It's important to not only check for the presence of a user in the user state but also to account for both the isLoading and user states.

Take a look at this example (even though it doesn't utilize Next.js, the underlying concept remains consistent):

https://codesandbox.io/s/async-data-context-w-skeleton-enbfy?fontsize=14&hidenavigation=1&theme=dark

Another strategy could involve temporarily blocking the entire page and utilizing the getInitialProps (gIP) function to pass the retrieved user value to the provider and component (values returned from gIP are included in

pageProps</code).</p>
<p>An alternative option to using context might be implementing a <a href="https://github.com/kirill-konshin/next-redux-wrapper" rel="nofollow noreferrer">Redux provider</a>, which enables setting and fetching global application states via gIP. However, this introduces added complexity in configuring Redux actions/reducers/constants and managing server-side cookie retrieval on the client-side, making it less developer-friendly overall.</p>
</div></answer1>
<exanswer1><div class="answer" i="64709175" l="4.0" c="1604628509" m="1604641924" a="TWF0dCBDYXJsb3R0YQ==" ai="7376526">
<p>When handling authenticated pages, one effective solution is to incorporate a temporary skeleton layout while the authentication process unfolds. Just like how <a href="https://i.stack.imgur.com/0KqSz.png" rel="nofollow noreferrer">YouTube</a> manages it, consider displaying a placeholder until the <code>isLoading
value transitions from true to false. Remember to evaluate not just the existence of a user in the user state, but also take into consideration both the isLoading and user states.

See this demonstration (despite not being based on Next.js, the core principle remains unchanged):

https://codesandbox.io/s/async-data-context-w-skeleton-enbfy?fontsize=14&hidenavigation=1&theme=dark

Another potential approach involves temporary page blocking and leveraging the getInitialProps (gIP) function to transmit the acquired user value to the provider and component (values fetched from gIP get incorporated into

pageProps</code).></p>
<p>An alternate method to using context could entail integrating a <a href="https://github.com/kirill-konshin/next-redux-wrapper" rel="nofollow noreferrer">Redux provider</a>, offering the flexibility to manage global application states through gIP. Nevertheless, this adds complexity in terms of setting up Redux actions/reducers/constants and dealing with server-side cookie retrieval on the client side, potentially making it a less convenient choice for developers.</p>
    </div></answer1>
<exanswer1><div class="answer" i="64709175" l="4.0" c="1604628509" m="1604641924" a="TWF0dCBDYXJsb3R0YQ==" ai="7376526">
<p>If you're working with authenticated pages, a smart move would be to introduce a skeleton structure while the authentication process runs its course. Much like how <a href="https://i.stack.imgur.com/0KqSz.png" rel="nofollow noreferrer">youtube</a> does it, think about showing a temporary placeholder <strong>until</strong> the <code>isLoading
status changes to false (defaulting initially to true). Essentially, don't solely rely on whether there's a user in the user state, but instead factor in both the isLoading state and user state.

Check out this demo (though not in Next, the idea remains intact):

https://codesandbox.io/s/async-data-context-w-skeleton-enbfy?fontsize=14&hidenavigation=1&theme=dark

As an alternative, you could block the whole page and use getInitialProps (gIP) to pass the resulting user value to the provider and component (any values returned by gIP will be added to pageProps). However, this isn't advisable because it: Excludes static optimization, leads to slower TTFB (time to first byte), and technically runs for each page navigation due to the inability to access app contextual values within gIP.

Instead of context, consider using a Redux provider, enabling you to set and retrieve global application state via gIP. But beware of the increased complexity involved: Setting up Redux with actions/reducers/constants and navigating the nuances of fetching cookies from the server using client-side cookies. In sum, not very friendly for developers.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the difference between TypeScript's import/as and import/require syntax?

In my coding project involving TypeScript and Express/Node.js, I've come across different import syntax options. The TypeScript Handbook suggests using import express = require('express');, while the typescript.d.ts file shows import * as ex ...

Exploring TypeScript's Index Types: Introduction to Enforcing Constraints on T[K]

In typescript, we can utilize index types to perform operations on specific properties: interface Sample { title: string; creationDate: Date; } function manipulateProperty<T, K extends keyof T>(obj: T, propName: K): void { obj[propName] ...

I encountered an error with no matching overload when attempting to make a call using the Tanstack query function

While I was successfully calling a single data in my database using the useEffect hook, now I am attempting to learn how to use tanstack@query. Unfortunately, I encountered an error when trying to call it. No overload matches this call. Overload 1 of 3, ...

Error encountered: The function store.getState is not defined in the Next.js application

I encountered an issue while creating a redux store in my Next.js and React.js app. The error message "Uncaught TypeError: store.getState is not a function" appears when I try to access the store without using a provider. Can someone assist me with resolvi ...

Using Angular to Bind Checkbox Value in Typescript

I have a challenge of creating a quiz where a card is displayed with 4 questions structured like this: <div class="col-md-6"> <div class="option" id="Answer1"> <label class="Answer1"> <input value= "Answer1" type="checkbox ...

Issue with Nuxt2 CompositionAPI: Unable to showcase imported render function in component - Error message states "template or render function not defined"

I have created a render function that I believe is valid. I am importing it into a component and registering it within the defineComponent. However, when running the code, I encounter an error saying "template or render function not defined". I am confide ...

Can you specify the necessary import statement for CallableContext?

My Google Cloud function is simple and looks like this: import * as functions from 'firebase-functions'; var util = require('util') export const repeat = functions.https.onCall( function (data, context) { console.log(&apo ...

Acessing files from Azure Blob within the Aurelia UI: Download or View now!

I currently have my files stored in Azure and I am looking for a way to either download or view them on the client side. This is how I envision the process: Azure -> Api -> Client UI (Aurelia) While I have come across several C# examples, I am unsu ...

Employing a section of an intricate map found in the Stores React platform

Take a look at this data stored in Zustand or any other store. productMap: { 'product-id-abc': { info: { name: 'Gmail', url: 'gmail.com', api: 'localhost:8080' }, endpo ...

The type 'number' cannot be assigned to the type 'Element'

Currently, I am developing a custom hook called useArray in React with TypeScript. This hook handles array methods such as push, update, remove, etc. It works perfectly fine in JavaScript, but encounters errors in TypeScript. Below is the snippet of code f ...

I'm encountering an issue with one of my routes not loading correctly in Angular 4 Universal

I have been working on implementing Universal and I believe I've made significant progress. My project is built on this seed. However, when I run "npm start", only the /about and /contact pages render successfully. The /home page does not render at al ...

What is the best way to utilize imported classes, functions, and variables within an Angular 2 template?

I've come up with a solution for incorporating static content into a template, but I'm unsure if it's the best approach. I would like to know if there is an official or more efficient method of achieving this. Here's an example showcas ...

The files _buildmanifest.js and _ssgmanifest.js could not be loaded. The server returned a 404 error, indicating that the resources were not found

Upcoming: 12.3.4 React Version: 17.0.2 Node Version: 16.13.1 An error is persisting in my Next.js application where the console displays the following message on all pages I load: 404 Failed to load resource: the server responded with a status of 404 ( ...

The invocation of `prisma.profile.findUnique()` is invalid due to inconsistent column data. An invalid character 'u' was found at index 0, resulting in a malformed ObjectID

The project I'm working on is built using Next.js with Prisma and MongoDB integration. Below is the content of my Prisma schema file: generator client { provider = "prisma-client-js" } datasource db { provider = "mongodb" url = env("DATABA ...

The function parameter in Angular's ngModelChange behaves differently than $event

How can I pass a different parameter to the $event in the function? <div class='col-sm'> <label class="col-3 col-form-label">Origen</label> <div class="col-4"> <select ...

Is it possible to integrate lightweight charts as a component in Next.js and pass data through props?

How to Import and Use Lightweight Charts in React: const createChart = dynamic(() => import("lightweight-charts"), { ssr: false }); Here is an example of how to use it in a component and pass props: <createChart options={{ width: 70 ...

Understanding the differences between paths and parameters of routes in express.js

My express application has the following routes: // Get category by id innerRouter.get('/:id', categoriesController.getById) // Get all categories along with their subcategories innerRouter.get('/withSubcategories', categoriesControll ...

Can someone provide guidance on effectively implementing this JavaScript (TypeScript) Tree Recursion function?

I'm currently grappling with coding a recursive function, specifically one that involves "Tree Recursion". I could really use some guidance to steer me in the right direction. To better explain my dilemma, let's consider a basic example showcasi ...

Tips on looping through a dynamic FormControl using Angular2 and FormBuilder

I am facing an issue when trying to iterate over a dynamically generated FormControl in the HTML section. Instead of displaying the expected values, I am getting [object Object],[object Object] in the input field. Here is the provided data structure: { ...

Manipulating data with Angular 2 services: accessing and updating values

This code snippet is all about managing an array in Angular. The Injectable decorator is used to define a service called Svc with methods for setting and getting column definitions. import { Injectable } from '@angular/core'; @Injectable() ...