0

I'm trying to figure out how to set up a nextjs index.tsx page, that renders a page if the user is authenticated and another component if the user is not authenticated.

I can have the not authenticated component rendered properly, but I cannot have the authenticated page rendered correctly. I cant find a tutorial to explain how to put a page in the if statement so that the main nextjs index.tsx page renders the page I specify if there is an authenticated user.

I have an index.tsx in pages with:

import * as React from "react"
import { Box, Center,  Spinner, VStack } from "@chakra-ui/react"
import Head from "next/head"
// import NextLink from "next/link"

import { useMe } from "lib/hooks/useMe"
import { DashLayout } from "components/DashLayout"
import { AuthedHomeLayout } from "components/AuthedHomeLayout"
import LandingPage  from "components/landing/lp"
import { HomeLayout } from "components/HomeLayout"

export default function Home() {
  const { me, loading } = useMe()
  if (loading)
  return (
    <Center>
      <Spinner /> 
    </Center>
  )

  return (
    <Box>
      <Head>
        <title>test</title>
      </Head>

     
        <Center flexDir="column" w="100%">
          <VStack>
            
            {me?  <AuthedHomeLayout><DashLayout /></AuthedHomeLayout> : (
              <HomeLayout><LandingPage /></HomeLayout>
            )}
            
          </VStack>
        </Center>
      
    </Box>
  )
}

When I try this as an authenticated user, the DashLayout does load, but the links in it do not render.

The DashLayout has a set of links in it that form the pages of the dashboard:

import * as React from "react"
import { Box, Flex, Heading, Link, LinkProps, Stack, useColorModeValue } from "@chakra-ui/react"
import NextLink from "next/link"
import { useRouter } from "next/router"

const DashLayout: React.FC = ({ children }) => {
  return (
    <Box pt={10} pb={20} w="100%">
      
      <Flex flexWrap={{ base: "wrap", md: "unset" }}>
        <Box pos="relative">
          <Stack
            position="sticky"
            top="100px"
            minW={{ base: "unset", md: "200px" }}
            mr={8}
            flexDir={{ base: "row", md: "column" }}
            mb={{ base: 8, md: 0 }}
            spacing={{ base: 0, md: 4 }}
          >
            <ProfileLink href="/dash">Dashboard</ProfileLink>
            <ProfileLink href="/dash/library">Library</ProfileLink>
            <ProfileLink href="/dash/help">Help</ProfileLink>
            
            
          </Stack>
        </Box>
        <Box w="100%">{children}</Box>
      </Flex>
    </Box>
  )
}

export default DashLayout




interface ProfileLinkProps extends LinkProps {
  href: string
}
const ProfileLink: React.FC<ProfileLinkProps> = ({ href, ...props }) => {
  const { asPath } = useRouter()
  const isActive = asPath === href
  const activeColor = useColorModeValue("black", "white")
  const inactiveColor = useColorModeValue("gray.600", "gray.500")
  return (
    <NextLink href={href} passHref>
      <Link
        pr={4}
        h="25px"
        justifyContent={{ base: "center", md: "flex-start" }}
        textDecoration="none !important"
        color={isActive ? activeColor : inactiveColor}
        _hover={{ color: useColorModeValue("black", "white") }}
        fontWeight={isActive ? "semibold" : "normal"}
      >
        {props.children}
      </Link>
    </NextLink>
  )
}

The page I want to render if there is an auth user, is:

import * as React from "react"
import { gql } from "@apollo/client"
import { Center, Spinner, Stack, Text } from "@chakra-ui/react"

import { useUpdateMeMutation } from "lib/graphql"
import { useForm } from "lib/hooks/useForm"
import { useMe } from "lib/hooks/useMe"
import { useMutationHandler } from "lib/hooks/useMutationHandler"
import { UPLOAD_PATHS } from "lib/uploadPaths"
import Yup from "lib/yup"
import { ButtonGroup } from "components/ButtonGroup"
import { Form } from "components/Form"
import { withAuth } from "components/hoc/withAuth"
import { AuthedHomeLayout } from "components/AuthedHomeLayout"
import { ImageUploader } from "components/ImageUploader"
import { Input } from "components/Input"
import { DashLayout } from "components/DashLayout"

const _ = gql`
  mutation UpdateMe($data: UpdateUserInput!) {
    updateMe(data: $data) {
      ...Me
    }
  }
`

const ProfileSchema = Yup.object().shape({
  email: Yup.string().email().required("Required").nullIfEmpty(),
  firstName: Yup.string().required("Required").nullIfEmpty(),
  lastName: Yup.string().required("Required").nullIfEmpty(),
})
function Dash() {
  const { me, loading } = useMe()

  const handler = useMutationHandler()
  const [updateUser] = useUpdateMeMutation()

  const updateAvatar = (avatar: string | null) => {
    return handler(() => updateUser({ variables: { data: { avatar } } }), {
      onSuccess: (_, toast) => toast({ description: "Avatar updated." }),
    })
  }

  const defaultValues = {
    email: me?.email || "",
    firstName: me?.firstName || "",
    lastName: me?.lastName || "",
  }

  const form = useForm({ defaultValues, schema: ProfileSchema })

  const handleUpdate = (data: typeof defaultValues) => {
    return form.handler(() => updateUser({ variables: { data } }), {
      onSuccess: (_, toast) => {
        toast({ description: "Info updated!" })
        form.reset(data)
      },
    })
  }

  if (loading)
    return (
      <Center>
        <Spinner />
      </Center>
    )
  if (!me) return null
  return (
    <Stack spacing={6}>
      <Tile>
        <Text>alskjf</Text>
      </Tile>
      
    </Stack>
  )
}

Dash.getLayout = (page: React.ReactNode) => (
  <AuthedHomeLayout>
    <DashLayout>{page}</DashLayout>
  </AuthedHomeLayout>
)

export default withAuth(Dash)

I also tried defining the index.tsx condition as:

                {me? 
<Dash /> // Dash is defined as a page in the pages folder at dash/index
 ///<AuthedHomeLayout><DashLayout /></AuthedHomeLayout> 
: (
                  <HomeLayout><LandingPage /></HomeLayout>
                )}

How can I have index.tsx defined to render one page if there is an authed user and another if there is not?

I saw this post and tried using one of the suggestions it makes, as follows:

import Router from 'next/router';

{me?  Router.push('/dash') : (
              <HomeLayout><LandingPage /></HomeLayout>
            )}

When I try this, I get errors that read:

[{
    "resource": "/src/pages/index.tsx",
    "owner": "typescript",
    "code": "2322",
    "severity": 8,
    "message": "Type 'Element | Promise<boolean>' is not assignable to type 'ReactNode'.\n  Type 'Promise<boolean>' is not assignable to type 'ReactNode'.",
    "source": "ts",
    "startLineNumber": 32,
    "startColumn": 13,
    "endLineNumber": 34,
    "endColumn": 15,
    "relatedInformation": [
        {
            "startLineNumber": 1360,
            "startColumn": 9,
            "endLineNumber": 1360,
            "endColumn": 17,
            "message": "The expected type comes from property 'children' which is declared here on type 'IntrinsicAttributes & OmitCommonProps<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof StackProps> & StackProps & { ...; }'",
            "resource": "repo/node_modules/@types/react/index.d.ts"
        }
    ]
}]
Mel
  • 2,481
  • 26
  • 113
  • 273
  • If your component is rendering, and inner links are not maybe the component has something wrong. I think the logic is working – Tushar Shahi Nov 08 '22 at 08:19
  • The render when I click the home link only displays the list of links for the authed login. But when I click one of those links, it changes the render to be the page (dash/index). I can't figure out how to make the home link render the page when the authed user is there. – Mel Nov 08 '22 at 08:27
  • Can you provide us more details, such as the code of the first jsx file you are rendering and the next js project architecture. I'm asking, in order to check if the next js configuration is correct and that you have this in your main jsx component that you are rendering: – Azzam Michel Nov 10 '22 at 22:27
  • @AzzamMichel - the first jsx file to be rendered is index.tsx (copied above). The condition for where to go next is what Im trying to figure out. I can render the landing page (it's a set of components inside a layout), but I can't find a way to correctly render the page for the auth user view (which is a file saved in pages/dash/index.tsx) - also copied above. At the moment, I'm rendering the layout that dash/index uses. It only renders the layout, not the page content. – Mel Nov 10 '22 at 22:53
  • I want you to try implementing this logic: https://azeezatraheem.medium.com/implementing-authentication-redirects-in-next-js-c15907ec82b7 – Azzam Michel Nov 11 '22 at 08:37

1 Answers1

1

In the solutions you tried, the last one was almost correct. You were on the right path, that you should redirect the user to the /dash page if he is authenticated. But you were doing the redirection in the return statement of your component, which is not where you want to do any side effect logic.

Your attempt:

import Router from 'next/router';

{me?  Router.push('/dash') : (
   <HomeLayout><LandingPage /></HomeLayout>
)}

will not work because Router.push returns a <Promise<boolean>>.

Don't forget that React components must return React elements. In your case when the user is authenticated, you are returning a promise not a React element.

So your redirection (which is a side effect) should be done inside a useEffect hook.

In order to fix this, Next documentation provides a clear example of how to do it correctly. What you are looking for is the last code block of this section (the one just before this section).

Don't forget to use a valid router instance via the useRouter hook provided by next/router.

So your code now becomes something like:

import { useEffect } from 'react';
import { useRouter } from 'next/router';

// Whatever Component you were doing the redirect
const export YourComponent = () => {
    // your component hooks and states
    const { me, loading } = useMe();
    const router = useRouter();

    // Here is what you were missing
    useEffect(() => {
        if (me) {
            router.push('/dash');
        }
    }, [me]);

    // you can add a loader like you did before
    return loading ? (
        <Center><Spinner /></Center>
    ) : (
        <HomeLayout><LandingPage /></HomeLayout>
    );
};

It should be enough to get to what you're looking for.

As a side note, your first solution:

{me? 
    <Dash /> // Dash is defined as a page in the pages folder at dash/index
    ///<AuthedHomeLayout><DashLayout /></AuthedHomeLayout> 
: (
    <HomeLayout><LandingPage /></HomeLayout>
)}

cannot work, as <Dash /> is a Next Page which is associated with a route based on its file name. You can look at it like an entry point.

mlegrix
  • 799
  • 5
  • 12