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"
}
]
}]
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.