Pass page variables to layout in NextJS - reactjs

I'm migrating my blog from Jekyll to NextJS and want to achieve the same functionality with the site layout.
The idea is to have metatags defined in a single layout file and fill values with variables that are defined on a page level.
I saw several solutions, one of them is to define metatags in _app.js as described here:
NextJS pass class from page to layout component
but from my understanding so far as a newbie in React/NextJS, it's better to utilize pageProps, but I can't figure out how to code it.
So far I have
_app.js
import 'bootstrap/dist/css/bootstrap.css'
import Layout from '../components/layout/layout';
export default function Blog({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
layout.js
import { Fragment } from 'react';
import Image from 'next/image';
import Head from 'next/head';
import MainNavigation from './main-navigation';
export default function Layout({ children }) {
return (
<Fragment>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<title>{children.title}</title>
</Head>
<MainNavigation />
<main>
{children}
</main>
</Fragment>
);
}
HomePage.js
export default function HomePage() {
return <div>HomePage</div>
}

I wanted to stick to the original code examples from the official documentation, so left layout as described in question, just
<Layout>
not
<Layout metas={pageProps.metas} ...>
So I just had to define props using getStaticProps:
export async function getStaticProps() {
return { props: { title: 'HomePage' } }
}
export default function HomePage() {
return <div>HomePage</div>
}
and then call it in layout as
{children.props.title}
not
{children.title}
The latter one would work if I just define a regular js variable in HomePAge.js as described in another SO thread I referenced. But I'm not sure if this approach somehow affects static website generation, so I decided to use NextJS built-in feature getStaticProps.

You can take the props you want in pageProps and pass them to the layout component:
<Layout metas={pageProps.metas} ...>
And use them in the <Layout />:
export default function Layout({ metas, children }) {
return (
<Fragment>
<Head>
<title>{metas.title}</title>
</Head>
...
</Fragment>
);
}

Related

Extensible layout components in nextjs

I am creating an application in nextjs. I understand that I can generate a layout like the example given in the docs
import Navbar from './navbar'
import Footer from './footer'
export default function Layout({ children }) {
return (
<>
<Navbar />
<main>{children}</main>
<Footer />
</>
)
}
However, I would like to change the contents of the Navbar on a per page basis.
export default function ListPage() {
return {
/** I want to add a secondary nav to the Navbar on this page */
<>
<Navbar><MySecondaryNav></Navbar>
....
</>
}
}
export default function ClientPage() {
return {
/** I want to add a a different secondary nav to the Navbar on this page */
<>
<Navbar><ClientNavbar></Navbar>
....
</>
}
}
I also need the markup to be rendered server-side. Is it possible to achieve this in nextjs?
Yes, you can!
You could use useRouter and then depending on the path render the correct component in NavBar
import { useRouter } from "next/router";
const router = useRouter();
now you should have access to router.pathname, depending on the pathname just render the correct component inside NavBar
#Edit - SSR solution
If you want this SSR, then this is how you do it
in your _app.tsx/jsx you can use getServerSideProps and then get directly resolvedUrl
export default function App({ Component, pageProps }) {
return (
<Layout {...pageProps}>
<Component {...pageProps} />
</Layout>
);
}
export async function getServerSideProps({{req, res, resolvedUrl}) {
return { props: { resolvedUrl } }
}
At this point you should be receiving resolvedUrl and based on that render the required component.
If this wont work for you we might need to setup a codesandbox

There is a way to remove component <Header /> inside _app.tsx in one single page?

I have a component inside my _app.tsx file, This componennt is rendered in all pages, but I want that in my page payment.tsx this component doesnt exist, there is some way to do it?
Something like this should work:
// pages/payment
export default function Payment() {
// ...
}
Payment.hideHeader = true;
// pages/_app
import Header from '...';
export default function App({ Component, pageProps }) {
return (
<>
{!Component.hideHeader && <Header />}
<Component {...pageProps} />
</>
);
}
You might need to customize types, etc. as you're using TS. Refer this: https://nextjs.org/docs/basic-features/layouts#with-typescript.

Pages are reloaded instead of routed in shopify next js app

I followed Shopify's guide, until the end of 4th step, to develop a Next JS app and I've setup two pages (embedded app navigation), Home and Page1.
Now, when I click to open both pages, the app is doing a reload instead of routing...
You can see here the flickering issue - https://youtu.be/45RvYgxC7C0
Any help on this would be very appreciated.
_app.js
import React from "react";
import App from "next/app";
import Head from "next/head";
import { AppProvider } from "#shopify/polaris";
import { Provider } from "#shopify/app-bridge-react";
import Cookies from "js-cookie";
import "#shopify/polaris/dist/styles.css";
import "../css/styles.css";
import lang from "#shopify/polaris/locales/en.json";
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
const config = { apiKey: API_KEY, shopOrigin: Cookies.get("shopOrigin"), forceRedirect: true };
return (
<React.Fragment>
<Head>
<title>My App</title>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="favicon.ico" />
</Head>
<Provider config={config}>
<AppProvider i18n={lang}>
<Component {...pageProps} />
</AppProvider>
</Provider>
</React.Fragment>
);
}
}
home.js
import React from "react";
import { Page, Layout, Card, FooterHelp, Link } from "#shopify/polaris";
export default function Home() {
return (
<Page title="Home">
<Layout>
<Layout.Section>
<Card title="Online store dashboard" sectioned>
<p>View a summary of your online store’s performance.</p>
</Card>
</Layout.Section>
<Layout.Section>
<FooterHelp>
Learn more about{" "}
<Link url="#" external>
our app
</Link>
</FooterHelp>
</Layout.Section>
</Layout>
</Page>
);
}
Page1.js
import React from "react";
import { Page, Layout, Card, FooterHelp, Link } from "#shopify/polaris";
export default function Page1() {
return (
<Page title="Page1">
<Layout>
<Layout.Section>
<Card title="Online store dashboard" sectioned>
<p>View a summary of your online store’s performance.</p>
</Card>
</Layout.Section>
<Layout.Section>
<FooterHelp>
Learn more about{" "}
<Link url="#" external>
our app
</Link>
</FooterHelp>
</Layout.Section>
</Layout>
</Page>
);
}
When using Shopify's app-bridge, it has a default behavior of navigating to a new route within the iframe that holds your app (and thus completely reloading the app), whereas React implements a client-side router.
Shopify doesn't provide a 100% plug-and-play solution for using client-side routing, but they do make it pretty easy with their ClientRouter component.
The examples on that page are for react-router, not Next.js's router, but the same idea applies to next/router.
For example, a simple router component could look like:
import {useEffect, useContext} from 'react';
import Router, { useRouter } from "next/router";
import { Context as AppBridgeContext } from "#shopify/app-bridge-react";
import { Redirect } from "#shopify/app-bridge/actions";
import { RoutePropagator as ShopifyRoutePropagator } from "#shopify/app-bridge-react";
const RoutePropagator = () => {
const router = useRouter();
const { route } = router;
const appBridge = React.useContext(AppBridgeContext);
// Subscribe to appBridge changes - captures appBridge urls
// and sends them to Next.js router. Use useEffect hook to
// load once when component mounted
useEffect(() => {
appBridge.subscribe(Redirect.Action.APP, ({ path }) => {
Router.push(path);
});
}, []);
return appBridge && route ? (
<ShopifyRoutePropagator location={route} app={appBridge} />
) : null;
}
export default RoutePropagator;
After creating that component, drop it in the _app.js file inside the Shopify routers, for example:
<Provider config={config}>
<AppProvider i18n={translations}>
<RoutePropagator />
<ApolloProvider client={client}>
// child components
</ApolloProvider>
</AppProvider>
</Provider>
When _app loads, it will now subscribe to changes from appBridge and let appBridge know to send a signal to the client rather than reload the entire iframe. If you apply any routing within the app, such as one page to another, it will also now update the browser's address bar.
Everything works correctly, you are loading the whole page every time you request a new nextjs page. In order to have parts of your layout persistent between page loads, you need to move them to the _app.js.
Take a look at the official dynamic app layout example.
If you want to load a sub-section of the page without reloading the whole page you can use a query in combination with shallow routing e.g example.com/settings and example.com/settings?section='profile'

Only load Snipcart on specific page in Gatsby

I'm using Snipcart Plugin in Gatsby but the script gets loaded everywhere. Is is it possible with some sort of function to trigger the script on only 1 specific page and not entirely?
Below are the options I'm using in my Gatsby-config.js file
{
resolve: "gatsby-plugin-snipcart",
options: {
apiKey: process.env.SNIPCART_API,
autopop: true,
js: "https://cdn.snipcart.com/themes/v3.0.8/default/snipcart.js",
styles: "https://cdn.snipcart.com/themes/v3.0.8/default/snipcart.css",
jquery: false,
},
},
You should take a look at gatsby-plugin-snipcartv3. I believe the gatsby-plugin-snipcart is deprecated and is not working with Snipcart v3.
But as far as I know, there's no way to tell the plugin to load the script only on specific pages.
You could use Snipcart directly, not using the plugin, to have more control over it.
Let's say you have a layout.js file, wrapping content for your page, you can have a loadSnipcart flag that will load Snipcart files only when you need them.
Here's an example:
layout.js
import React from "react"
import Helmet from "react-helmet"
export default ({ loadSnipcart, children }) => {
const Snipcart = () => {
if (!loadSnipcart) return null
return (
<Helmet>
<script
src="https://cdn.snipcart.com/themes/v3.0.8/default/snipcart.js"
type="text/javascript"
/>
<link
href="https://cdn.snipcart.com/themes/v3.0.8/default/snipcart.css"
rel="stylesheet"
/>
</Helmet>
)
}
return (
<div id="main-content">
<Snipcart />
{children}
</div>
)
}
shop.js
import React from "react"
import Layout from "./layout"
export default () => {
return (
<Layout loadSnipcart>
<h1>Welcome to my shop !</h1>
</Layout>
)
}
index.js
import React from "react"
import Layout from "./layout"
export default () => {
return (
<Layout>
<h1>This page doesn't load Snipcart</h1>
</Layout>
)
}

Have a common header layout in nextjs

I have 2 pages user.js and nonuser.js and one component header. user.js and nonuser.js have same functionality with slight changes in UI. Now I want to integrate all this. Like when I visit the page by default table of user.js must be viewed. One click of nonuser.js it should change to the nonuser.js table. And I want header to be same for both, content in textbox should not change when I switch between pages.
I'm new to next.js and react
header.js
import React, { Component } from 'react';
import '../Header/header.css';
import { Menu, Input, Icon } from 'antd';
import Link from 'next/link';
class HeaderComponent extends Component {
render() {
return (
<div className="navbar">
<div className="header">
<div className="col-1">
<div className="menu">
<div>
<Link href="/User"><a>Users</a></Link>
</div>
<div>
<Link href="/nonUser"><a>Non Users</a></Link>
</div>
<Input className="text-box" placeholder="Enter name" prefix={<Icon type="search" ></Icon>}></Input>
</div>
</div>
</div>
</div>
)
}
}
export default HeaderComponent
user.js
class User extends Component {
render() {
return (
<React.Fragment>
<div className="ant-table-row">
<div className="table-head-text">
<span className="text">Users({data.length})</span>
<Pagination defaultCurrent={1} total={100} />
</div>
<Table
rowKey={data._id}
columns={this.columns1}
rowSelection={this.rowSelection}
onExpand={this.onExpand}
dataSource={data} />
</div>
</React.Fragment>
)
}
I didn't add nonuser component, its same as user component
index.js
import Header from '../components/Header/header';
import Layout from '../components/Layout';
function App() {
return (
<Header/>
<div>
</div>
)
}
export default App;
I've done this, On first landing the only header is there and on clicking user link in header, header disappears and only table of user is shown.
EDIT:
I tried this header appears in both and I placed a textbox in header .textbox value clears when I switch between pages.
user.js and nonuser.js
render(){
return(
<Layout>
<div>.....</div>
</Layout>
)
}
Also tried
index.js
render() {
return (
<Layout>
<div>
</div>
</Layout>
)
}
layout.js
const Layout = ({children}) => (
<div>
<Header></Header>
{children}
</div>
);
From what I make of your question, you want to use HeaderComponent as a common header for both pages? Then I'd suggest placing it in your components/Layout file. Next will wrap all pages in the layout component, thus adding your header to all pages.
I'm also wondering why you have an index.js file? Unless it's placed in pages/ folder, it isn't something you normally do in Next. The pages user.js and nonuser.js should also be placed in the pages/ folder. Next will then automatically load the to files and provide them under the routes /user and /nonuser (based on the name of the file). This will also make Next wrap each page in the layout component mentioned above.
I'd suggest looking into NextJS learning guide. It provides a very good introduction to NextJS and will make it a lot easier to use NextJS if you. They have a lesson explaining how to use Shared Components which explains exactly what you seem to be looking for.
Hope this helps a bit.
Edit:
Example using _app.js
The following is an example of how to use a custom layout component in next using _app.js. It's based on Nexts own example.
// components/Layout.js
import React, { Component } from 'react';
import Header from './Header';
class Layout extends Component {
render () {
const { children } = this.props
return (
<div className='layout'>
<Header />
{children}
</div>
);
}
}
// pages/_app.js
import React from 'react';
import App from 'next/app';
import Layout from '../components/Layout';
export default class MyApp extends App {
render () {
const { Component, pageProps } = this.props
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
}
To get more information on how to make use of _app.js properly, check out their documentation on custom app.

Resources