Nextjs routing in react - render a page if the user is authenticated - reactjs

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.

Related

Nothing was returned from render in react functional component

I am working with react to fetch data from the node backend and implement the UI with the data. I rendered the UI conditionally but I do get an error in the console saying that nothing was returned from render. Here is my code
import React, { useEffect, useState } from "react";
import OT from "#opentok/client";
import { OTSession, OTPublisher, OTStreams, getPublisher } from "opentok-react";
import Connection from "./Connection";
import Publisher from "./Publisher";
import Subscriber from "./Subscriber";
import { useParams } from "react-router-dom";
import { connect } from "react-redux";
import { Creators } from "../../../services/redux/event/actions";
import { PropTypes } from "prop-types";
import { makeStyles, Container } from "#material-ui/core";
function Host(props) {
const [connect, setConnect] = useState(false);
const params = useParams();
const { event, error, isCreatingEvent } = props;
console.log(event, "event");
const handleSessionOn = () => {
setConnect(true);
};
useEffect(() => {
props.getSingle(params.id);
}, []);
if (isCreatingEvent) {
return <div>Loading .....</div>;
}
if (error) {
return <div>Error: {error.error_message}</div>;
}
if (event.sessionId != undefined) {
const { API_KEY: apiKey, sessionId, token } = event;
console.log(apiKey, sessionId, token)
return (
<div style={{ zIndex: 100 }}>
<Connection connect={connect} />
<h3 style={{ color: "red" }}>This is apiKey connect</h3>
<OTSession
sessionId={sessionId}
token={token}
apiKey={apiKey}
onConnect={handleSessionOn}
>
<Publisher />
<OTStreams>
<Subscriber sessionId={sessionId} />
</OTStreams>
</OTSession>
</div>
)
}
}
Host.protoTypes = {
event: PropTypes.object.isRequired,
error: PropTypes.string,
};
const mapDispatchToProps = (dispatch) => {
return {
getSingle: (id) => {
dispatch(Creators.getOneEvent(id));
},
};
};
const mapStateToProps = (state) => (
console.log(state),
{
event: state.event.event,
error: state.event.error,
isCreatingEvent: state.event.isCreatingEvent,
}
);
export default connect(mapStateToProps, mapDispatchToProps)(Host);
Can anyone please help me out? I used the redux state to connect with Vonage API but the OTSession is not being rendered.
You called the return function only on the if statement.
You should call the return function on the else statement.
Like this.
const { API_KEY: apiKey, sessionId, token } = event;
{event.sessionId != undefined ? (
<div style={{ zIndex: 100 }}>
<Connection connect={connect} />
<h3 style={{ color: "red" }}>This is apiKey connect</h3>
<OTSession
sessionId={sessionId}
token={token}
apiKey={apiKey}
onConnect={handleSessionOn}
>
<Publisher />
<OTStreams>
<Subscriber sessionId={sessionId} />
</OTStreams>
</OTSession>
</div>
) : null}

Next.js with Sanity not building Blog pages

I'm working on my first integration of Sanity with Next.Js, trying to add a blog to a personal site. Dev works fine, but when I go to deploy, or do a build, I get an error that it can't find one of the props for the blog page.
Error thrown is this:
TypeError: Cannot read property 'title' of undefined
This is what I'm using for my [slug].js file:
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import Head from 'next/head';
import { useRouter } from 'next/router';
import Layout from '../../components/layout';
import Scrollbar from 'react-scrollbars-custom';
import Transitions from '../../lib/transitions';
import BlockContent from '#sanity/block-content-to-react';
import { postQuery, postSlugsQuery } from '../../lib/grocQueries';
import { getClient, overlayDrafts, sanityClient } from '../../lib/sanity.server';
import { urlForImage, usePreviewSubscription } from '../../lib/sanity';
const pageVariants = Transitions.pageVariant;
const pageTransition = Transitions.pageTransition;
export const Post = ({ data = {}, preview }) => {
const router = useRouter();
const slug = data?.post?.slug;
const {
data: { post, morePosts },
} = usePreviewSubscription(postQuery, {
params: { slug },
initialData: data,
enabled: preview && slug,
});
return (
<Layout>
<motion.article className='blog-article' initial='initial' animate='in' exit='out' variants={pageVariants} transition={pageTransition}>
<Scrollbar style={{ width: '100%', height: '100%' }}>
<figure className='hero-container'>
<h1 className='blog-title'>{post.title} </h1>
{post.mainImage && <img className='blog-hero' alt='Some alt Text' src={urlForImage(post.mainImage).url()} />}
</figure>
<div className='copy-block'>
<BlockContent blocks={post.body} imageOptions={{ w: 860, fit: 'max' }} {...sanityClient.config()} />
</div>
</Scrollbar>
</motion.article>
</Layout>
);
};
export async function getStaticProps({ params, preview = false }) {
const { post, morePosts } = await getClient(preview).fetch(postQuery, {
slug: params.slug,
});
return {
props: {
preview,
data: {
post,
morePosts: overlayDrafts(morePosts),
},
},
};
}
export async function getStaticPaths() {
const paths = await sanityClient.fetch(postSlugsQuery);
return {
paths: paths.map((slug) => ({ params: { slug } })),
fallback: true,
};
}
export default Post;
<figure className='hero-container'>
<h1 className='blog-title'>{post?.title} </h1>
{post?.mainImage && <img className='blog-hero' alt='Some alt Text' src={urlForImage(post?.mainImage).url()} />}
</figure>
During the build time next not aware of the post object and its key so it's better to make an optional chaining.
There are 2 things may want to add a in static generated page so it knows what to do if there isn't any props.
Add props checking in page, so it will not fail build.
if (!props) return null // or !data in your case.
Add notFound return props in getStaticProps so it knows how to handle 404 properly.
return {
notFound: true,
revalidate: 300,
}

Hide and Display menu Core UI

I integrated the template Core UI in my application.
The redirection is configured on _nav.js like presented by that picture:
I'm asking if it's possible to hide or display a menu depending on such condition ?.
For Exemple: Show Public Student and Hide Manage Convention depending on a condition.
The menu is defined on _nav.js
export default [
{
_tag: 'CSidebarNavTitle',
_children: ['Menu'],
},
{
_tag: 'CSidebarNavItem',
name: 'Public Space',
to: '/home',
}, // ...
{
_tag: 'CSidebarNavItem',
name: 'Manage Convention',
to: '/manageConvention',
} // ...
]
Then, this Array is called on TheSidebar.js
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { CCreateElement, CSidebar, CSidebarBrand, CSidebarNav, CSidebarNavDivider, CSidebarNavTitle, CSidebarMinimizer, CSidebarNavDropdown, CSidebarNavItem } from '#coreui/react'
import CIcon from '#coreui/icons-react'
// sidebar nav config
import navigation from './_nav'
const TheSidebar = () => {
const dispatch = useDispatch()
const show = useSelector(state => state.sidebarShow)
return (
<CSidebar
show={show}
onShowChange={(val) => dispatch({type: 'set', sidebarShow: val })}
>
<CSidebarBrand className="d-md-down-none" to="/">
<CIcon
className="c-sidebar-brand-full"
name="logo-negative"
height={35}
/>
<CIcon
className="c-sidebar-brand-minimized"
name="sygnet"
height={35}
/>
</CSidebarBrand>
<CSidebarNav>
<CCreateElement
items={navigation}
components={{
CSidebarNavDivider,
CSidebarNavDropdown,
CSidebarNavItem,
CSidebarNavTitle
}}
/>
</CSidebarNav>
<CSidebarMinimizer className="c-d-md-down-none"/>
</CSidebar>
)
}
export default React.memo(TheSidebar)
Any suggestion will be appreciated.Big Thanks.
I know it's an old question but:
I think the simplest solution would be to add a meta prop inside _nav.js to every "CNavItem"
//_nav.js
const _nav = [
{
component: CNavItem,
name: 'Locations',
to: '/location',
meta: { role: ['Admin', 'Recruiter'] },
icon: <CIcon icon={cilLocationPin} customClassName="nav-icon" />
},
]
Helper function
export default function hasAccess(userRole, roles) {
if (Array.isArray(userRole)) {
return roles.some((r) => userRole.map((item) => item.toLowerCase()).includes(r.toLowerCase()))
} else {
return roles.map((item) => item.toLowerCase()).includes(userRole.toLowerCase())
}
}
After, inside AppSidebarNav.js component, check if the nav item includes at least one of the user role. If it does, render it, otherwise no.
userRole it's returned from redux store in this case. It's up to you how you get the logged in user roles.
//AppSidebarNav.js
export const AppSidebarNav = ({ items }) => {
const userRole = useSelector((state) => state.user.role)
{...}
const navItem = (item, index) => {
const { component, name, badge, icon, meta, ...rest } = item
const Component = component
return (
hasAccess(userRole, item.meta?.role || []) && <Component
{...(rest.to &&
!rest.items && {
component: NavLink,
})}
key={index}
{...rest}
>
{navLink(name, icon, badge)}
</Component>
)
}
{...}
return (
<React.Fragment>
{items &&
items.map((item, index) => (item.items ? (navGroup(item, index)) : navItem(item, index)))}
</React.Fragment>
)
}
You can use a child component class SidebarChild in TheSidebar.js to precise the condition that you want.
Then, inject it in <CSidebar >.
and don't forget to use the constant const SBChild = connect()(SidebarChild) to be able to call SBChild inside <CSidebar >.
Hope To Help.
Reading your comment, you can call an API to check for authorization (preferrably, just after login) and save it to state. You can use it as a flag as I've done below:
Try Changing the true on first line below to false.
const getWorldAccess = () => true;//Call API for user access rights here and save it to a state
const World = () => <div>World</div>;
const Hello = () => {
return (
<div>Hello {getWorldAccess() && <World />}</div>
);//replace getWorldAccess() with a state variable (you don't want to call API infinitely)
}
ReactDOM.render( <
Hello / > ,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
You can filter the _nav.js array within the TheSideBar.js according to the condition you want. That will hide it from displaying in sidebar. You will have to know what the index of the item you want to remove.
I think this is the best answer to your question.
First, you have to divide items in the _nav.js file according to the below structure. (This allows, to export the navbar items according to user levels).
import React from 'react'
import CIcon from '#coreui/icons-react'
var navStaff = {
items: [
{
_tag: 'CSidebarNavItem',
name: 'Dashboard',
to: '/dashboard',
icon: <CIcon name="cil-speedometer" customClasses="c-sidebar-nav-icon"/>,
}
]
};
var navAdmin = {
items: [
{
_tag: 'CSidebarNavItem',
name: 'Dashboard',
to: '/dashboard',
icon: <CIcon name="cil-speedometer" customClasses="c-sidebar-nav-icon"/>,
},
{
_tag: 'CSidebarNavTitle',
_children: ['USERS']
},
{
_tag: 'CSidebarNavItem',
name: 'Users',
to: '/users',
icon: 'cil-people',
}
]
};
export {navStaff, navAdmin };
Then your TheSidebar.js should look like this which is used to allow different content in the navbar. If you want you can use the local storage to allow only the logged user type to allocate the selected navbar.
import React, { lazy, useState, useEffect, useRef} from 'react'
import { useSelector, useDispatch } from 'react-redux'
import {
CCreateElement,
CSidebar,
CSidebarBrand,
CSidebarNav,
CSidebarNavDivider,
CSidebarNavTitle,
CNavItem,
CProgress,
CSidebarMinimizer,
CSidebarNavDropdown,
CSidebarNavItem,
} from '#coreui/react'
import CIcon from '#coreui/icons-react'
// sidebar nav config
import {navStaff, navAdmin} from './_nav'
const TheSidebar = () => {
console.log(navStaff)
const dispatch = useDispatch()
const show = useSelector(state => state.sidebarShow);
return (
<CSidebar
show={show}
unfoldable
onShowChange={(val) => dispatch({type: 'set', sidebarShow: val })}
>
<CSidebarBrand className="d-md-down-none" to="/">
<CIcon
className="c-sidebar-brand-minimized"
name="sygnet"
height={35}
/>
</CSidebarBrand>
<CSidebarNav>
<CCreateElement
items={navAdmin.items}
components={{
CSidebarNavDivider,
CSidebarNavDropdown,
CSidebarNavItem,
CSidebarNavTitle
}}
/>
<CSidebarNavDivider />
</CSidebarNav>
<CSidebarMinimizer className="c-d-md-down-none"/>
</CSidebar>
)
}
export default React.memo(TheSidebar)
Hope you get the answer which you need. Thanks.
In the component, CSidebar just set value of minimize to true

Redux loses state when navigating to another page in Next.js

I'm creating the redux state in this page :
import React from 'react';
import { connect } from 'react-redux';
import styled from 'styled-components';
import wrapper from '../redux/store';
import Container from '../components/Container/Container';
import Card from '../components/Card/Card';
import Circle from '../components/Circle/Circle';
import PieChart from '../components/PieChart/PieChart';
import Accordion from '../components/Accordion/Accordion';
import RadioButton from '../components/Ui/RadioButton/RadioButton';
import { manageList, reportList } from '../components/helper';
import { getManageListAndCategoryId } from '../redux/actions/actions';
const Panel = ({ manageProductsList }) => (
<>
{console.log(manageProductsList)}
<MainContainer>
<Title>Управление</Title>
<ContainersWrapper>
{manageProductsList.map((item, index) => <Card key={index} title={item.title} type="service" serviceName={item.value} />)}
</ContainersWrapper>
<SecondSection>
<CustomContainer>
<Title>Отчетность</Title>
<p>Показатели за:</p>
Здесь будут ТАБЫ
<ContainersWrapper>
{reportList.map((item, index) => <Card key={index} item={item} type="report" />)}
</ContainersWrapper>
<DiagreammWrapper>
<PieChart />
<Circle percent={20} />
<Circle percent={87} />
<Circle percent={30} />
<Circle percent={47} />
</DiagreammWrapper>
</CustomContainer>
</SecondSection>
<CustomContainer>
<TitleTwo>Доступные отчеты</TitleTwo>
<Accordion />
<RadioButton />
</CustomContainer>
</MainContainer>
</>
);
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(getManageListAndCategoryId(manageList));
});
const mapStateToProps = (state) => ({
manageProductsList: state.mainReducer.manageProductsList,
});
export default connect(mapStateToProps, null)(Panel);
And I still can see the data manageProductsList (screenshot) in Redux in this page. But when I navigate to another dynamic route page forms/[id.tsx]
import React from 'react';
import { connect } from 'react-redux';
import wrapper from '../redux/store';
import { util, manageList, reportList } from '../../components/helper';
import { getManageListAndCategoryId } from '../../redux/actions/actions';
export async function getStaticPaths(categoryIds) {
console.log('categoryIds', categoryIds);
//temporarely make static path data while categoryIds is undefined
const paths = [
{ params: { id: 'object' } },
{ params: { id: 'service' } },
{ params: { id: 'club_cards' } },
{ params: { id: 'schedule' } },
{ params: { id: 'agents' } },
{ params: { id: 'abonements' } },
{ params: { id: 'price_category' } },
{ params: { id: 'person_data' } },
{ params: { id: 'roles' } },
];
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params, manageProductsList }) {
// const postData = util.findFormData(params.id, manageProductsList);
const postData = { title: 'asdsadasdsad' };
return {
props: {
postData,
},
};
}
const Form = ({ manageProductsList }) => (
<div>
{console.log(manageProductsList)}
{/* {postData.title} */}
dasdsadsad
</div>
);
const mapStateToProps = (state) => ({
categoryIds: state.mainReducer.categoryIds,
manageProductsList: state.mainReducer.manageProductsList,
});
export default connect(mapStateToProps, null)(Form);
the manageProductsList and categoryIds are empty arrays (screenshot 2)
I am using native Link from next/link component to navigate the page
Here is Card component which navigate to dynamic page:
import React, { FunctionComponent, HTMLAttributes } from 'react';
import styled from 'styled-components';
import Link from 'next/link';
import EditIcon from '#material-ui/icons/Edit';
import AddIcon from '#material-ui/icons/Add';
interface CardProps extends HTMLAttributes<HTMLOrSVGElement>{
title: string
type: string
item?: {
title: string
amount: number
}
serviceName: string
}
const Card: FunctionComponent<CardProps> = ({
type, title, serviceName, item,
}) => (
<>
{
type === 'service'
&& (
<FirstSection>
<h1>{title}</h1>
<ImageWrapper>
<Link href={`/forms/${serviceName}`}>
<a><AddIcon fontSize="large" onClick={(e) => { console.log(serviceName); }} /></a>
</Link>
<EditIcon />
</ImageWrapper>
</FirstSection>
)
}
{
type === 'report'
&& (
<SecondSection>
<h1>{item.title}</h1>
<p>{item.amount}</p>
</SecondSection>
)
}
</>
);
export default Card;
I would be very gratefull if someone can help
Your <Link> will cause server-side rendering, you can observe whether the browser tab is loading or not when navigate to another page. If it is, the page will reload and the redux state would be refresh.
The official docs shows the right way for using dynamic route.
<Link href="/forms/[id]" as={`/forms/${serviceName}`}>

TypeError: undefined is not an object(evaluating '_props.listMessagesQuery.listMessages') in ReactNative

Iam new to react-native and aws appsync.We are trying to display a list of messages.But when i run react-native run-android it is throwing an error saying
TypeError: undefined is not an object(evaluating '_props.listMessagesQuery.listMessages')
[Below is the screenshot url of the error]
https://i.stack.imgur.com/b1Wlj.png
Chat.js
import React,{Component} from 'react';
import ChatInput from './ChatInput';
import ChatMessages from './ChatMessages';
import { graphql, compose } from 'react-apollo';
import listMessages from './querys/listMessages';
import createMessage from './querys/createMessage';
import gql from 'graphql-tag';
import {
Platform,
StyleSheet,
Text,
View,
scrollIntoView
} from 'react-native';
class Chat extends Component {
state = {
message: '',
}
render() {
return (
<View className='Chat'>
<ChatMessages
messages={this.props.listMessagesQuery.listMessages || []}
endRef={this._endRef}
/>
<ChatInput
message={this.state.message}
onTextInput={(message) => this.setState({message})}
onResetText={() => this.setState({message: ''})}
onSend={this._onSend}
/>
</View>
);
}
_onSend = () => {
//console.log(`Send: ${this.state.message}`)
this.props.createMessageMutation({
variables: {
text: this.state.message,
sentById: this.props.userId
}
})
}
/*
* AUTO SCROLLING
*/
_endRef = (element) => {
this.endRef = element
}
componentDidUpdate(prevProps) {
// scroll down with every new message
if (prevProps.listMessagesQuery.listMessages !== this.props.listMessagesQuery.listMessages && this.endRef) {
this.endRef.scrollIntoView()
}
}
}
export default compose(
graphql(listMessages, {
options: {
fetchPolicy: 'cache-and-network'
},
props: (props) => ({
posts: props.listMessagesQuery.listMessages && props.listMessagesQuery.listMessages.Message,
})
}))(Chat)
App.js
import React,{ Component} from 'react';
import * as AWS from 'aws-sdk';
import {
Platform,
StyleSheet,
Text,
View
} from 'react-native';
import gql from 'graphql-tag';
import { graphql,compose} from 'react-apollo';
import generateStupidName from 'sillyname';
import localStorage from 'react-native-sync-localstorage';
import Chat from './Chat';
import { Async } from 'react-async-await';
import createPerson from './querys/createPerson';
const CHAT_USER_NAME_KEY = 'CHAT_USER_NAME'
const CHAT_USER_ID_KEY = 'CHAT_USER_ID'
class App extends Component {
async componentDidMount() {
let name = localStorage.getItem(CHAT_USER_NAME_KEY)
if (!name) {
name = generateStupidName()
const result = await this.props.createPersonMutation({
variables: { name }
})
localStorage.setItem(CHAT_USER_NAME_KEY, result.data.createPerson.name);
localStorage.setItem(CHAT_USER_ID_KEY, result.data.createPerson.id);
}
}
render() {
const name = localStorage.getItem(CHAT_USER_NAME_KEY)
const userId = localStorage.getItem(CHAT_USER_ID_KEY)
return (
<View style={styles.container}>
<Chat name={name} userId={userId} />
</View>
);
}
}
// const createPerson =gql`
// mutation createPerson($name:String!){
// createPerson(input :{
// name : $name
// }){
// id
// name
// }
// }
// `
// export default graphql(createPerson,{name:'createPersonMutation'})(App)
export default compose(
graphql(createPerson, {name:'createPersonMutation'}))(App)
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
Iam not understanding this error Please help me.Thanks! in Advance
Please check the format of
this.props.listMessagesQuery.listMessages
As the error defined that particular data or props you are passing are not an object.
console.log(this.props.listMessagesQuery.listMessages)
check if you find it in current formate. If you don't find anything share you this console.log result. Hope it helps you
you are not sending listMessagesQuery.listMessages as a props to Chat.js component you are only sending name and userId as props to Chat component
your existing code in App.js
<Chat name={name} userId={userId} />
you need to send
<Chat name={name} userId={userId} listMessagesQuery={}/>

Resources