NextJS per page layout not working with typescript - node.js

I am developing new application in NextJS 12 using typescript. I have defined two pages register and home page and i want to apply different layout to this pages, i have followed official next js documentation for this, i can see the "Registration Page" text in browser but layout not applying on page output, am i missing something in code? below is my code.
register.tsx
const UserRegistration: NextPageWithLayout = () => {
return <h1>Registration Page</h1>
}
UserRegistration.getLayout = (page: ReactElement) => {
return (
<DefaultLayout>{page}</DefaultLayout>
)
}
export default UserRegistration;
_app.tsx
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
export default MyApp
type.ts
export type NextPageWithLayout = NextPage & { getLayout: (page: ReactElement) => ReactNode };
export type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout }
export type DefaultLayoutType = { children: ReactNode }
layout.tsx
const DefaultLayout = ({ children }: DefaultLayoutType) => {
return(
<div id="main">
<nav>
<li>
Home
</li>
</nav>
{children}
</div>
)
}
export default DefaultLayout;

Related

Can you use templates in Next.js?

I am fairly new to web development and currently have a rudimentary web server using Node.js, Express, and Pug which I am hoping to convert to Next.js. Is it possible to create re-usable templates (similar to Pug/Jade) in Next.js?
This is how I do mine. There are better ways, but it's how I like it. I came from express handlebars, and have used pug before, so this is how I did mine.
In pages/_app.js file:
import React from 'react'
import Head from 'next/head'
export default function MyApp({ Component, pageProps }) {
const Layout = Component.Layout || LayoutEmpty // if page has no layout, it uses blank layout
const PageTitle = Component.PageTitle // page title of the page
return (
<Layout>
{PageTitle? (<Head><title>{PageTitle}</title></Head>) : '' }
<Component {...pageProps} />
</Layout>
)
}
const LayoutEmpty = ({children}) => <>{children}</> // blank layout if doesnt detect any layout
In your component folder where ever you want to put your layout file: eg component/layout.js
import Link from 'next/link';
import {useRouter} from 'next/router'
export function LayoutOne({children}) {
try {
return (<>
<nav>
<ul>
<li><Link href="/"><a>Home</a></Link></li>
</ul>
</nav>
<div>{children}</div>
</>)
} catch(e) {console.log(e)}
}
Then in your pages: eg pages/about.js
import React, { useState, useEffect } from 'react';
import {LayoutOne} from '../component/layout' // location of your layout.js file
Aboutpage.PageTitle = 'About | Website Tag Line' // set title of your page
Aboutpage.Layout = LayoutOne // using LayoutOne. If you dont do this, its considered blank layout and you'll get no layout
export default function Aboutpage() {
try {
return (
<>
<div>
<h2>About</h2>
</div>
</>
);
} catch(e) {console.log(e)}
}
If you want more layout, in your layout.js file at the end, just change the name of the export function eg: LayoutTwo
export function LayoutTwo({children}) {
try {
return (<>
<nav>
<ul>
<li><Link href="/dashboard"><a>Dashboard</a></Link></li>
</ul>
</nav>
<div>{children}</div>
</>)
} catch(e) {console.log(e)}
}
And one the page, you change layout to two
import {LayoutTwo} from '../component/layout'
Aboutpage.Layout = LayoutTwo

React, update component after async function set

I want to add data and see in below, and also when I start app, I want see added records. But I can see it, when I'm try to writing something in the fields.
The thing is, the function that updates the static list is asynchronous. This function retrieves data from the database, but before assigning it to a variable, the page has been rendered. There is some way to wait for this variable or update information other way than when you try to type it in the fields. This is before the form is approved.
[![enter image description here][1]][1]
class AddAdvertisment extends React.Component <any, any> {
private advertisment;
constructor(props, state:IAdvertisment){
super(props);
this.onButtonClick = this.onButtonClick.bind(this);
this.state = state;
this.advertisment = new Advertisement(props);
}
onButtonClick(){
this.advertisment.add(this.getAmount(), this.state.name, this.state.description, this.state.date);
this.setState(state => ({ showRecords: true }));
}
updateName(evt){
this.setState(state => ({ name: evt.target.value }));
}
....
render() {
return (<React.Fragment>
<div className={styles.form}>
<section className={styles.section}>
<input id="name" onChange={this.updateName.bind(this)} ></input>
<input id="description" onChange={this.updateDescription.bind(this)} ></input>
<input type="date" id="date" onChange={this.updateDate.bind(this)} ></input>
<button className={styles.action_button} onClick={this.onButtonClick.bind(this)}>Add</button>
</section>
</div>
{<ShowAdvertismentList/>}
</React.Fragment>
);
}
class ShowAdvertismentList extends React.Component <any, any>{
render(){
let listItems;
let array = Advertisement.ad
if(array !== undefined){
listItems = array.map((item) =>
<React.Fragment>
<div className={styles.record}>
<p key={item.id+"a"} >Advertisment name is: {item.name}</p>
<p key={item.id+"b"} >Description: {item.description}</p>
<p key={item.id+"c"} >Date: {item.date}</p>
</div>
</React.Fragment>
);
}
return <div className={styles.adv_show}>{listItems}</div>;
class Advertisement extends React.Component {
public static ad:[IAdvertisment];
constructor(props){
super(props);
if(!Advertisement.ad){
this.select_from_db();
}
}
....
select_from_db = async () => {
const res = await fetch('http://localhost:8000/select');
const odp = await res.json();
if(odp !== "brak danych")
odp.forEach(element => {
if(Advertisement.ad){
Advertisement.ad.push(element);
}
else{
Advertisement.ad = [element];
I try to create function and child like:
function Select_from_db(){
const[items, setItems] = useState();
useEffect(() => {
fetch('http://localhost:8000/select')
.then(res => res.json())
.then(data => setItems(data));
}, []);
return <div className={styles.adv_show}>{items && <Child items={items}/>}
</div>;
}
function Child({items}){
return(
<>
{items.map(item => ( ...
))}
</>
And is working good in first moment, but if I want add item to db I must refresh page to see it on a list below.
I use is instead ShowAdvertismentList in render function. Elements be added to db but not showing below. In next click is this same, until refresh page.
And in my opinio better use a list, becouse I musn't want to conect to database every time to download all records.
[1]: https://i.stack.imgur.com/IYSNU.gif
I now recipe. I must change state on componentDidMount in AddAdvertisment class.
async componentDidMount(){
let z = await setTimeout(() => {
this.setState(state => ({ loaded: true}));
}, 1000);
}
render() {
return (<React.Fragment >
(...)
{this.state.loaded ? <ShowAdvertismentList /> : <Loading/>}
</React.Fragment>
);
}

React how to pass state without rendering component

I have an issue where when I start at the root and click through the links all the states load in fine but when I got to copy and paste the URL into a new window it doesnt show, would I be right to assume its because the previous component isnt being rendered to set the state?
Hopefully someone can point me to some helpful articles or even have experienced this before?
Here is a link to a screen recording I made to better show what I mean https://youtu.be/M0390D4oJDg
CODE
import React from "react";
import { useLocation, useParams } from "react-router";
import PostBlock from "./PostBlock";
const PostList = () => {
const {thread} = useParams()
const { state: { description, title } = {} } = useLocation();
return (
<div>
<div className="m-10 flex justify-center">
<div style={{ width: "1216px" }}>
<div className="flex justify-center">
<div className="flex-1 justify-center mb-5 p-5 h-64 border border-gray-300">
<div>{title}</div>
<div>{description}</div>
</div>
</div>
<table className="min-w-full table-auto">
<thead className="justify-between">
<tr className="bg-gray-800">
<th className="px-8 py-2 text-left text-white">Posts</th>
</tr>
</thead>
<tbody className="bg-gray-200">
<PostBlock thread={thread}/>
</tbody>
</table>
</div>
</div>
</div>
);
};
export default PostList;
LINK
<Link to={{ pathname: `/thread/${thread}`, state: {title: title, description: description} }}>
URL
http://localhost:3000/thread/jxvgOKPrSiCnI2ocDxgJ
This react stuff is harder than I initially thought
Don't give up easily. It's one of the beautiful things!
Start with optional chaining.
const title = useLocation()?.state?.title;
const description = useLocation()?.state?.description;
If this is not supported, use &&:
const title = useLocation() && useLocation().state && useLocation().state.title;
const description = useLocation() && useLocation().state && useLocation().state.description;
And only when title and description is not null, render your contents.
const PostList = () => {
const { thread } = useParams();
const title = useLocation() && useLocation().state && useLocation().state.title;
const description = useLocation() && useLocation().state && useLocation().state.description;
return title && description ? <div></div> : null;
};
Now the code will not render the content until and unless both the values are set. And this will make sure your code loads without any errors.
I am a bit confused as to what your question really is, but you've got some issues with your code. Try this:
const PostList = () => {
const { thread } = useParams();
const { state: { description, title } = {} } = useLocation();
return <div></div>;
};
There is no reason to initialize useLocation() twice. You could've also initialized it into one constant and then access description and state through it.
const PostList = () => {
const { thread } = useParams();
const location = useLocation();
console.log(location.state.title, location.state.description);
return <div></div>;
};
Additionally, you can incorporate object?.property method as a failsafe if your state is empty:
console.log(location?.state?.title);
Take a look at proper useLocation usage here

Nextjs how to not unmount previous page when going to next page (to keep state)

we are using Nextjs in our web app.
We want to keep stack of pages where users visit to keep state of component on back navigation.
How should we do that?
I have tried https://github.com/exogen/next-modal-pages, but it calls getInitialProps of previous pages again on back.
Here's my solution with a custom _app.js
import React, { useRef, useEffect, memo } from 'react'
import { useRouter } from 'next/router'
const ROUTES_TO_RETAIN = ['/dashboard', '/top', '/recent', 'my-posts']
const App = ({ Component, pageProps }) => {
const router = useRouter()
const retainedComponents = useRef({})
const isRetainableRoute = ROUTES_TO_RETAIN.includes(router.asPath)
// Add Component to retainedComponents if we haven't got it already
if (isRetainableRoute && !retainedComponents.current[router.asPath]) {
const MemoComponent = memo(Component)
retainedComponents.current[router.asPath] = {
component: <MemoComponent {...pageProps} />,
scrollPos: 0
}
}
// Save the scroll position of current page before leaving
const handleRouteChangeStart = url => {
if (isRetainableRoute) {
retainedComponents.current[router.asPath].scrollPos = window.scrollY
}
}
// Save scroll position - requires an up-to-date router.asPath
useEffect(() => {
router.events.on('routeChangeStart', handleRouteChangeStart)
return () => {
router.events.off('routeChangeStart', handleRouteChangeStart)
}
}, [router.asPath])
// Scroll to the saved position when we load a retained component
useEffect(() => {
if (isRetainableRoute) {
window.scrollTo(0, retainedComponents.current[router.asPath].scrollPos)
}
}, [Component, pageProps])
return (
<div>
<div style={{ display: isRetainableRoute ? 'block' : 'none' }}>
{Object.entries(retainedComponents.current).map(([path, c]) => (
<div
key={path}
style={{ display: router.asPath === path ? 'block' : 'none' }}
>
{c.component}
</div>
))}
</div>
{!isRetainableRoute && <Component {...pageProps} />}
</div>
)
}
export default App
Gist - https://gist.github.com/GusRuss89/df05ea25310043fc38a5e2ba3cb0c016
You can't "save the state of the page by not un-mounting it" but you can save the state of your app in _app.js file, and the rebuild the previous page from it.
Check the redux example from next's repo.

Cannot read property 'allContentfulBlogPost' of undefined" after moving query from index.js to component in GatsbyJS (with Contenful and GraphQL)

Moving a query from index.js to midsection.js (a component) gives Cannot read property of undefined.
I made a website with GatsbyJS which gets it's content from Contentful. I accomplished this by following the Build a blazing fast website with GatsbyJS and Contentful tutorial: https://www.youtube.com/watch?v=wlIdop5Yv_Y
In the tutorial you learn the basics of making a query which shows your content from Contentful on the homepage.
Because I like to use Bulma and I'm pretty new to GatsbyJS (new to React as well) I decided to download the Gatsby-Bulma-Quickstart (https://www.gatsbyjs.org/starters/amandeepmittal/gatsby-bulma-quickstart) and compare it to my own website and use what I need.
I decided to use the component structure used in the Quickstart and wanted to move the query for getting my content from the index.js to the midsection.js.
I got everything working until I moved the query.
My index.js looked like this:
import React from 'react'
import { Link } from 'gatsby'
// import Layout from '../components/layout';
const BlogPost = ({node}) => {
return (
<li>
<Link to={node.slug}><h3>{node.title}</h3></Link>
<img src={node.heroImage.resize.src} />
<div>{node.description.childMarkdownRemark.excerpt}</div>
</li>
)
}
const IndexPage = ({data}) => (
<ul className='blog-post'>
{data.allContentfulBlogPost.edges.map((edge) => <BlogPost node={edge.node} />)}
</ul>
)
// const IndexPage = () => <Layout />;
export default IndexPage
export const pageQuery = graphql`
query pageQuery {
allContentfulBlogPost (filter: {
node_locale: {eq: "en-US"}
},
sort:{ fields: [publishDate], order: DESC }
) {
edges {
node {
title
slug
description {
childMarkdownRemark {
excerpt
}
}
heroImage {
resize(width: 300, height: 300) {
src
}
}
}
}
}
}
`
Note: This works, this shows my content. (But as you can see the components etc from the Quickstart are not included (yet))
This is what my index.js looks like right now:
import React from 'react'
import Layout from '../components/layout';
const IndexPage = () => <Layout />;
export default IndexPage
And this is what my midsection.js looks like right now:
import React from 'react'
import { Link } from 'gatsby'
import './style.scss'
const BlogPost = ({node}) => {
return (
<li>
<Link to={node.slug}><h3>{node.title}</h3></Link>
<img src={node.heroImage.resize.src} />
<div>{node.description.childMarkdownRemark.excerpt}</div>
</li>
)
}
const Midsection = ({data}) => (
<ul className="blog-post">
{data.allContentfulBlogPost.edges.map((edge) => <BlogPost node={edge.node} />)}
</ul>
)
export default Midsection
export const pageQuery = graphql`
query pageQuery {
allContentfulBlogPost (filter: {
node_locale: {eq: "en-US"}
},
sort:{ fields: [publishDate], order: DESC }
) {
edges {
node {
title
slug
description {
childMarkdownRemark {
excerpt
}
}
heroImage {
resize(width: 300, height: 300) {
src
}
}
}
}
}
}
`
Using this way of moving the query to a component gives this error in the browser:
TypeError: Cannot read property 'allContentfulBlogPost' of undefined
I'd expected to use the midsection.js component for columns to show available "blog posts" from Contentful. Instead this only works straight from index.js.
Is there some way the query is not working because I moved it from the root folder to the components folder? And if so, what do I need to do to get the result I want?
With an colleague helping me, we found an solution by following these steps:
Change layout.js to:
import './style.scss'
const Layout = ({ children }) => children
export default Layout
Change index.js to:
import React from 'react'
import Layout from '../components/layout';
import Helmet from '../components/helmet';
import Header from '../components/header';
import Midsection from '../components/midsection';
import Footer from '../components/footer';
const IndexPage = ({data}) => (
<Layout>
<Helmet />
<Header />
<Midsection posts={data.allContentfulBlogPost.edges}/>
<Footer />
</Layout>
)
export default IndexPage
export const pageQuery = graphql`
query pageQuery {
allContentfulBlogPost (filter: {
node_locale: {eq: "en-US"}
},
sort:{ fields: [publishDate], order: DESC }
) {
edges {
node {
title
slug
description {
childMarkdownRemark {
excerpt
}
}
heroImage {
resize(width: 300, height: 300) {
src
}
}
}
}
}
}
`
Change midsection.js to:
import React from 'react'
import Link from 'gatsby-link'
import './style.scss'
const BlogPost = ({node}) => {
return (
<li>
<Link to={node.slug}><h3>{node.title}</h3></Link>
<img src={node.heroImage.resize.src} />
<div>{node.description.childMarkdownRemark.excerpt}</div>
</li>
)
}
const Midsection = ({ posts }) => (
<ul className="blog-post">
{posts.map(post => (
<BlogPost key={post.node.slug} node={post.node} />
))}
</ul>
)
export default Midsection
So what was the problem and what solved it?
The query used in this situation is a pageQuery which means that it only works from pages found in the pages folder. If you want to use the data in a component you have to pass it through :)

Resources