Is it possible to access the current selected layout segment from a component placed inside a root layout?
i.e.
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<head />
<body>
<NavMenu session={session} />
<GlobalHero /> {/* <- This is where I want to access the current route */}
{children}
</body>
</html>
);
}
// importing this causes a runtime error
// Error: Cannot read properties of null (reading 'useContext')
import { useSelectedLayoutSegment } from "next/navigation"
export default function GlobalHero() {
return ( <div> .... </div>)
}
Related
I was testing how server and client component interact and came up with something that I don't understand. Here is the code:
// layout.js
"use client";
/* ... imports */
export const Test = createContext();
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<Test.Provider value={"haha"}>
<Header />
{children}
<Footer />
</Test.Provider>
</body>
</html>
);
}
// Header
function Header() {
/* ...imports */
const haha = useContext(Test);
return (
<header>
{haha}
</header>
);
}
Header is a server component, so executed and rendered on the server. But it is still able to access the context which is being provided by the client layout component. How is this possible?
learning react/nextjs...i have a main layout that simply includes two components - footer, and header.
function MainLayout(children) {
return(
<div>
<Header />
{children}
<Footer />
</div>
);
}
export default MainLayout;
i want this layout to sit within the main page like so:
import {default as Layout} from './MLayout.js';
import {default as Navbar} from './MNavbar.js';
function MainPage() {
return(
<div>
<Layout children={Navbar} />
</div>
);
}
export default MainPage;
and within that mainpage, i'd like to insert a navbar in between the footer and header of the mainlayout. the navbar will change later, so that's why i want to keep it as a child.
the navbar im using does not use links, it uses buttons instead:
const Navbar = (props, {children}) => {
const [login, makeLogin] = useState(false);
function Login(){
makeLogin(true);
}
if (login) {
return (
<div>
<LoginPage />
</div>
)
}
else {
return ( blah blah blah the regular nav bar buttons.
);
}};
the error i get:
Unhandled Runtime Error
Error: Objects are not valid as a React child (found: object with keys {children}). If you meant to render a collection of children, use an array instead.
Pass <Navbar/> as children in MainPage to
<MainLayout children={<Navbar />} />
Get { children } in
function MainLayout({ children }) {...}
Destructure the props for your MainLayout and Navbar components by wrapping them in curly brackets:
function MainLayout({ children }) {
const Navbar = ({ children }) => {
currently your code is passing the entire props object, which looks like { children: ... } and is causing your error.
I have been using react storefront for a couple of weeks now. Though I understood high level concepts, I am really stuck for API implementation using fulfillAPIRequest, fetchFromAPI, appData and pageData etc.
My current code is as follows:
File: _app.js:
export default function MyApp({ Component, pageProps }) {
useJssStyles()
const classes = useStyles()
const [appData] = useAppStore(pageProps || {})
return (
<PWA errorReporter={reportError}>
<Head>
{/* <meta
key="viewport"
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
/> */}
</Head>
<SessionProvider url="/api/session">
<MuiThemeProvider theme={theme}>
<CssBaseline/>
<Header className={classes.header} />
<HeaderBanner />
<Delivery />
<Search />
<main className={classes.main}>
<Component {...pageProps} />
</main>
<Footer />
</MuiThemeProvider>
</SessionProvider>
</PWA>
)
}
MyApp.getInitialProps = async function({ Component, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
console.info("Executing _app.js: Invoking Component.getInitialProps...")
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
file: pages/index.js => entry point page
import React from 'react'
import { fetchFromAPI, createLazyProps } from 'react-storefront/props'
import NearbyShops from '../components/shops/nearby-shops.js'
export default function Index(lazyProps) {
return (
<NearbyShops />
)
}
Index.getInitialProps = createLazyProps(fetchFromAPI, { timeout: 50 })
I implemented api for the page index.js, in the foder /pages/api/index.js, as follows...
import fulfillApiRequest from 'react-storefront/props/fulfillAPIRequest'
function getPageData () {
console.info("Executing 'getPageData' in /api/index.js...")
return Promise.resolve({})
}
export default async function Index (params, req, res) {
console.info("Executing '/pages/api/index.js' ...")
const result = await fulfillApiRequest(req, {
appData: () => getAppData(),
pageData: () => getPageData()
})
return result
}
When I run this app, I get the following error in the GUI:
In the console, I get the error stack trace as follows...
If I don't use the fulfillAPIRequest and its related api methods, I am able to show the rendered page, normally. But now I want to integrate the API. The official documentation is not that helpful in this regard.
Please help.
I would like to create and use this component in React with ES6:
<Menu>
<MenuHeader>Hi</MenuHeader>
<MenuItem>Hello</MenuItem>
<MenuFooter>End</MenuFooter>
</Menu>
I've defined a component to handle this structure:
export class Menu extends React.Component {
render() {
return (
<div ...>
<div ...>
{HOW TO SELECT HEADER?}
</div>
<div ...>
{HOW TO SELECT ITEM?}
</div>
<div ...>
{HOW TO SELECT FOOTER?}
</div>
</div>
)}
}
It's okay to iterate over children and select by type.name while running on the dev server without transpilation:
{ React.Children.map(this.props.children, child => { return child.props.type === 'MenuItem' ? <>{ child }</> : '' } ) }
But it does not work after building it (cause of uglify/minify process).
For example, Semantic UI React handles it well - but it uses interfaces and written in TypeScript so I cannot use it as reference.
And one more thing (ah Steve:): I do not want to use npm eject.
This is normally done by allowing the compound components inside them to render their own children and Menu would just render the children it gets, hence maintaining the order.
You might want to share the state of things happening between the Header, Body and Footer, so we add a ContextProvider to the Menu component, so they can all share common state.
const rootEl = document.getElementById('root');
const { render } = ReactDOM;
const { createContext } = React;
function MenuHeader({ children }) {
return (
<header className="menu-header">
{children}
</header>
)
}
function MenuBody({ children }) {
return (
<div className="menu-body">
{children}
</div>
)
}
const MenuContext = createContext();
Menu.Header = MenuHeader;
Menu.Body = MenuBody;
function Menu({ children }) {
return (
<MenuContext.Provider value={null}>
<div className="menu-wrapper">
{children}
</div>
</MenuContext.Provider>
);
}
function App() {
return (
<Menu>
<Menu.Header>Menu Header</Menu.Header>
<Menu.Body>Menu Body</Menu.Body>
</Menu>
);
}
render(<App />, rootEl);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root" />
Another common technique used by Frameworks like Ant.Design is to map over the children and add common props to them (although context would be the better solution in my opinion)
I'm thinking of migrating from create-react-app to Next.js
I need to share component between routes in Next.js
I tried the Layout file example below and it worked pretty well for me but I have a special case where I need the shared component to be above the router itself.
For example, if I have a video element and want the video to still playing if I changed the route
const Layout = (props) => (
<div>
{props.children}
<video width="400" controls>
<source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4"/>
Your browser does not support HTML5 video.
</video>
</div>
)
const Home = () => (
<Layout>
<Link href="/about"><a>About</a></Link>
<h1 className='title'>Welcome to Home!</h1>
</Layout>
)
const About = () => (
<Layout>
<Link href="/"><a>Home</a></Link>
<h1 className='title'>Welcome to About!</h1>
</Layout>
)
Example from another project using create-react-app and reach-router
the player component is above the router
<Layout>
<Player/>
<Router className={'router'}>
<Login path={Routes.login}/>
<NotFound404 default/>
<Home path={Routes.root}/>
<Test path={Routes.test}/>
</Router>
<Layout/>
The shared element will be the video tag, the problem is on every route change the video is rendered and replayed
You might wanna check the docs on using pages/_app.js for that.
Example usage is shown below:
import Head from "next/head";
type Props = {
pageProps: any;
Component: React.FC;
};
const App = ({ Component, pageProps }: Props) => {
return (
<>
<Head>
// You can put whatever you want to put in your document head
</Head>
<YourSharedComponent />
<Component {...pageProps} />
<style>{`
// styles...
`}</style>
</>
);
};
App.getInitialProps = async ({ Component }) => {
const pageProps = Component.getInitialProps && await Component.getInitialProps(ctx, loggedUser);
return {
pageProps,
}
};
export default App;