Im trying to get some informations with api in my header. But not works. my codes.
export async function getStaticProps() {
console.log("test")
const res = await fetch(`https://api.namefake.com`)
const data = await res.json()
if (!data) {
return {
notFound: true,
}
}
return {
props: { data },
}
}
I put console.log in function for to test whether the function is runs. but nothing shows up in console. I tried this in _app.js and It worked but I want to use in Header. Im using layout for pages.
layout :
import Header from './header'
export default function Layout({ children }) {
return(
<>
<Header/>
{children}
</>
);
}
_app.js
import Layout from './layout'
function MyApp({ Component, pageProps }) {
<Layout>
<Component {...pageProps} />
</Layout>
</>
);
}
export default MyApp
functions like getStaticProps() getServerSideProps() are server side functions and does not get passed to client side and so it works only in page components (pages/index.js ) and it does not in components which usually get passed to client bundle
Related
I'm taking my first pass at using NextJS, and also my first pass at using a headless CMS (DatoCMS in this case).
Everything was actually working fine, but I found myself using a lot of prop drilling, to get information returned from the CMS down into deeply nested components, and that was becoming cumbersome. After some googling, I decided to try to use React Context to avoid prop drilling as described here.
The first use case is getting site branding info and menu items down to the nav component (this info will almost always be the same, everywhere on the site, but there might be some sections (e.g., landing pages) where they're different).
The problem is, the React useState set function as passed through the ContextProvider doesn't actually seem to set the state. There are several questions on here about state not being updated in various scenarios, but this isn't setting the initial state after NextJS grabs the data from getStaticProps, but here it's not doing it even if I manually call the set function directly in the component wrapped in ContextProvider, never mind somewhere farther down the stack.
As described in that blog post, I have the context provider pulled out into its own component:
// context/header.js
import { createContext, useContext, useState } from 'react';
const HeaderContext = createContext();
export function HeaderProvider({children}) {
const [header, setHeader] = useState(null);
const value = { header, setHeader };
return (
<HeaderContext.Provider value={value}>
{children}
</HeaderContext.Provider>
)
}
export function useHeader() {
return useContext(HeaderContext);
}
Then, in _app.js, I wrap my <Header /> component in the context provider:
// pages/_app.js
import Header from '../components/Header';
import { HeaderProvider } from '../lib/context/header';
function MyApp({ Component, pageProps }) {
return (
<>
<HeaderProvider>
<Header { ...pageProps } />
</HeaderProvider>
<Component {...pageProps} />
</>
)
}
export default MyApp
I'm just starting this out, so right now, everything is designed to be a static page (no server side rendering or client side React yet).
The main page, from index.js, successfully grabs the data from DatoCMS:
// pages/index.js
import { request } from "../lib/datocms";
import { HOMEPAGE_QUERY } from '../lib/query';
export async function getStaticProps() {
const data = await request({
query: HOMEPAGE_QUERY
});
return {
props: { data }
};
}
export default function Home({ data }) {
return (
<div>
This is the home page
</div>
);
}
So far, this is working, because in my <Header /> component, I can log the data passed.
As I understand how NextJS works, that getStaticProps call populates pageProps which get passed back to the Component (the page) to generate the static html. The results of the request call in getStaticProps is in fact making it's way back to pageProps to be used both in the <Component /> and the <Header /> in _app.js, but when I try to use the setHeader() function from header.js, pulled in by calling useHeader(), the value of the stateful header is always null.
// components/Header.js
import React, { useEffect } from 'react'
import { useHeader } from '../lib/context/header';
import Navbar from './Navbar';
export default function Header({data}) {
const HeaderData = {
siteName: data.siteBranding.siteName,
companyLogo: data.siteBranding.companyLogo,
menu: data?.menu
};
console.log(HeaderData);
// outputs:
// {
// siteName: 'My Site',
// companyLogo: {
// alt: 'My Site Logo',
// height: 113,
// url: 'https://www.datocms-assets.com/<url>',
// width: 122
// },
// menu: { title: 'primary', menuItems: [ [Object], [Object] ] }
// }
const { setHeader } = useHeader();
useEffect(() => {
setHeader(HeaderData)
}, [HeaderData]);
return (
<header>
<Navbar />
</header>
)
}
And then using the context in <Navbar />:
import React from 'react';
import { useHeader } from '../lib/context/header';
export default function Navbar() {
const { header } = useHeader();
console.log(header); // null :(
return (
<div>This is my nav</div>
)
}
How do I actually get my context state into <Navbar />?
The problem is, the header context in <Navbar /> is always the initial state of null. There was a similar question, but it wasn't using NextJS, just React, and the answer there seemed to be that he was trying to use the useContext call outside of the component chain he had wrapped in ContextProvider. Maybe I'm missing something, but I'm pretty sure that's not my issue.
So, I did the natural debugging thing, and started adding a bunch of console.logs. Before I even get to the <Navbar /> component, it appears that the setHeader() call isn't actually updating the state.
Here's an updated <Header />:
export default function Header({data}) {
const HeaderData = {
siteName: data.siteBranding.siteName,
companyLogo: data.siteBranding.companyLogo,
menu: data?.menu
};
console.log('Set HeaderData to');
console.log(HeaderData);
// In the initial code, I just pulled in setHeader here, but now pulling in header for debugging purposes
const { header, setHeader } = useHeader();
setHeader(HeaderData); // various incantations here
console.log('Just set header, header is');
console.log(header)
But no matter what incantation I use at "various incantations here", the result logged to console is always the same:
Set HeaderData to
{
siteName: 'My Site',
companyLogo: {
alt: 'My Site Logo',
height: 113,
url: 'https://www.datocms-assets.com/<url>',
width: 122
},
menu: { title: 'primary', menuItems: [ [Object], [Object] ] }
}
Just set header, header is
null
I've tried setHeader(HeaderData), which seems most analogous to what he did in that original blog post, but since that wasn't working, I also tried setHeader({HeaderData}) and setHeader({...HeaderData}), but the results are identical.
Why isn't this set setting?
There are a few issues with the code as written. Firstly you shouldn't try to console log the new state value after setting it. It won't work. Calling setHeader doesn't change the value of header in this call. It causes it to be changed for the next rendering of the component (which will happen immediately).
Secondly, don't use useEffect to synchronize your state.
const HeaderData = {
siteName: data.siteBranding.siteName,
companyLogo: data.siteBranding.companyLogo,
menu: data?.menu
};
const { setHeader } = useHeader();
useEffect(() => {
setHeader(HeaderData)
}, [HeaderData]);
This code fragment used in any component which is an ancestor of a HeaderContext will cause an infinite render loop. The useEffect will fire each time HeaderData changes. But HeaderData is constructed each time the component is rendered. So the useEffect will fire each time the component is rendered, and it will call setHeader which will force a re-render, which closes the loop.
If you're trying to simply specify the initial state (and not trying to update it in response to some event), simply pass the initial state to the provider component. e.g.
// context/header.js
import { createContext, useContext, useState } from 'react';
const HeaderContext = createContext();
const defaultHeaderState = {
siteName: data.siteBranding.siteName,
companyLogo: data.siteBranding.companyLogo,
menu: data?.menu
};
export function HeaderProvider({
initialState = defaultHeaderState,
children
}) {
const [header, setHeader] = useState(initialState);
const value = { header, setHeader };
return (
<HeaderContext.Provider value={value}>
{children}
</HeaderContext.Provider>
)
}
export function useHeader() {
return useContext(HeaderContext);
}
Chad's answer was definitely right, directionally — don't use useEffect, and "just set it". The problem was,
how to set it from data coming in via getStaticProps?
The answer was to extract it at the _app.js level from pageProps, and pass it directly as a value to the context provider. In fact, there's no longer even a reason to use setState:
// pages/_app.js
import { HeaderProvider } from '../lib/context/header';
function MyApp({ Component, pageProps }) {
// pull the initial data out of pageProps
const headerData = {
urgentBanner: pageProps.data?.urgentBanner,
siteName: pageProps.data.siteBranding.siteName,
companyLogo: pageProps.data.siteBranding.companyLogo,
menu: pageProps.data?.menu
}
return (
<>
{ /* pass it as a value to the context provider */ }
<HeaderProvider value={headerData}>
<Header { ...pageProps } />
</HeaderProvider>
<Component {...pageProps} />
</>
)
}
and the context provider component gets simplified down to
// context/header.js
import { createContext, useContext } from 'react';
const HeaderContext = createContext();
export function HeaderProvider({value, children}) {
return (
<HeaderContext.Provider value={value}>
{children}
</HeaderContext.Provider>
)
}
export function useHeader() {
return useContext(HeaderContext);
}
Finally, everywhere you want to use it (in a descendent of the context provider, HeaderProvider), you can just:
import { useHeader } from '../lib/context/header';
const headerData = useHeader();
It's possible to pass all the props of the page to all its children components implicitly (without passing as parameter)?
I need something like this:
export default function Home({ user }) {
if (!user) {
return (
<>
<component1/>
<component2/>
<component3/>
</>
);
} else {
return (
<></>
);
}
}
export async function getServerSideProps(context) {
const { Auth } = withSSRContext(context);
try {
const user = await Auth.currentAuthenticatedUser();
return {
props: {
user: JSON.parse(JSON.stringify(user)),
}
}
}
I need that user is available to component1, component2, component3 and all its sub-components without passing explicitly.
I've read that you can use context for this. But I'm using amplify aws and I don't know if it's possible ...
First your components should be capitalize.
You can use context to pass the values without the use of the props.
For that you need to setup context using createContext
import { createContext } from "react";
const UserContext = createContext()
export default function Home({ user }) {
if (user) {
return (
<UserContext.Provider value={user}>
<Component1/>
<Component2/>
<Component3/>
</UserContext.Provider>
);
} else {
return (
<></>
);
}}
And then inside the Component1 or 2 or 3 ... You can use useContext to get the value.
import { useContext } from "react"
function Component1() {
const user = useContext(UserContext);
return (
<>
<h1>Component 1</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}
You can use react context, redux, recoil. Recoil is easy to use.
Context provides a way to pass data through the component tree without having to pass props down manually at every level, It's react concept, AWS Amplify will not restrict from using this. Feel free to use it here.
I'm using Next.JS in an application where I have a Navbar component that is needed on all pages of the application, and the Navbar renders data (specifically product-categories) that must be fetched from my database.
I want this component to be server-side rendered, but since Next.JS only supports page-level SSR and not component-level SSR, it seems that I must use getServerSideProps on all pages that I want to display this Navbar and write the same API request in each one, resulting in a ton of repeated logic and API calls across several pages.
While researching how to solve this, I came across React Server Components and wonder if that would be a valid solution for this scenario, but since I'm new to the concept, I'm not sure if I understand it correctly, hence why I'm writing this question.
I'm thinking of doing the following and want to get some feedback as to whether I am on the right track.
My Navbar component would be something like as follows:
const Navbar = () => {
const [data, setData] = useState();
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
const data = await fetch("/api/someEndpoint");
setData(data);
};
return (
<div>
{data.someProperty}
{data.someOtherProperty}
</div>
);
};
export default Navbar;
Then I can create a global Layout component so that the Navbar is included in all pages, which is where React.Suspense would be used (if I understand it correctly):
const Layout = ({ children }) => {
return (
<>
<React.Suspense fallback={<FallbackComponent />}>
<Navbar />
<React.Suspense />
{children}
</>
);
};
export default Layout;
Then in _app.tsx, I would include my Layout component so that the Navbar is present everywhere:
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
So here is my question: is this a correct implementation and valid use-case of React.Suspense? Is component-level data fetching one of the proposed benefits of React Server Components?
Now with the Next.JS 13 version, you can create an app directory that uses react server components by default.
But you should keep in mind that most of the React hooks like useState and useEffect won't work on server components, also previous Next.js APIs such as getServerSideProps, getStaticProps, and getInitialProps are not supported in the new app directory.
So instead, you can use the new fetch() API that was built on top of the native fetch() Web API. Check the docs for more details.
Instead of fetching via an API, you could instead use SSR in _app.tsx that would pass the data as props to the MyApp hook. Then the data could be passed down further into the Layout component, which would again pass it down even further to the Navbar component. So it would look like something along the lines of this:
// _app.tsx
function MyApp({ /* Props */, data }) {
return (
<Layout data={data}>
<Component {...pageProps} />
</Layout>
);
}
export function getServerSideProps(context) {
// Get data
return {
props: {
data: data
},
}
}
// Layout.tsx
const Layout = ({ children, data }) => {
return (
<>
<Navbar data={data} />
{children}
</>
);
};
export default Layout;
// Navbar.tsx
const Navbar = ({ data }) => {
return (
<div>
{data.someProperty}
{data.someOtherProperty}
</div>
);
};
export default Navbar;
EDIT: SSR in _app.tsx doesn't work, but I found an answer here: https://stackoverflow.com/a/72325973/12190941
EDIT 2:
You can use getInitalProps in _app.js if you want your entire site to be server side rendered. Do keep in mind that this will increase initial load times. I found this example in the Next.js docs here
// _app.js
// ...
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
return { ...appProps }
Essentially I created a HOC for two pages in my Next.js app (i.e. profile and dashboard) two prevent users from accessing them if they're not authorized.
Example: pages/profile.js
import withAuth from "../components/AuthCheck/index";
function Profile() {
return (
<>
<h1>Profile</h1>
</>
)
}
export default withAuth(Profile);
My Auth component/HOC:
import { useRouter } from 'next/router'
import { useUser } from '../../lib/hooks'
import { PageLoader } from '../Loader/index'
const withAuth = Component => {
const Auth = (props) => {
const { isError } = useUser(); //My hook which is calling /api/user see if there is a user
const router = useRouter()
if (isError === 'Unauthorized') {
if (typeof window !== 'undefined' && router.pathname === '/profile' || router.pathname === 'dashboard') router.push('/login')
return <PageLoader />
}
return (
<Component {...props} />
);
};
if (Component.getInitialProps) {
Auth.getInitialProps = Component.getInitialProps;
}
return Auth;
};
export default withAuth;
Now what is happening is if you happen to enter /profile or /dashboard in the browser URL bar, before the redirect you'll see the page for a second i.e. flash.
Any idea why that is happening?
I'd consider making use of getServerSideProps on pages that need to be authed, have a look at getServerSideProps . It'll run server side on a page per request.
Alternatively, it might make sense (depending on your project setup - especially if you have access to auth state in _app.tsx_) to render the auth component in your _app. More specifically, you could add a protected:true prop to the pages that are behind auth wall (using static props). Then in app you can check if a particular page has protected===true and redirect to auth component if the user isn't authed, for example:
{pageProps.protected && !user ? (
<LoginComponent />
) : (
<Component {...pageProps} />
)}
Based on what Juliomalves and Adrian mentioned I re-read the Next.js docs based on what they included, Always good to get a refresh.
That being said I tried what Adian posted.
In the _app.js file I did the following:
import dynamic from "next/dynamic";
import { useRouter } from 'next/router'
import { useEffect } from 'react';
import { PageLoader } from '../components/Loader/index'
import { useUser } from '../lib/hooks'
import Login from '../pages/login'
const Layout = dynamic(() => import('../components/Layout'));
function MyApp({ Component, pageProps }) {
const { user, isLoading } = useUser();
const router = useRouter();
useEffect(() => {
router.replace(router.pathname, undefined, { shallow: true })
}, [user])
function AuthLogin() {
useEffect(() => {
router.replace('/login', undefined, { shallow: true })
}, [])
return <Login />
}
return (
<Layout>
{isLoading ? <PageLoader /> :
pageProps.auth && !user ? (
<AuthLogin />
) : (
<Component {...pageProps} />
)
}
</Layout>
);
}
export default MyApp
So the isLoading prop from the SWR hook useUser() is a part of the first conditional ternary, While true you get the <Loader/>, when false you get the next ternary to kick of;
If the both the auth and !user props are true, the AuthLogin get rendered!
This is how I did it. I went into the pages I wanted private and used the async function getStaticProps and created the prop auth and set it to true.
/pages/dashboard.js Or whatever you want to be private;
export default function Dashboard() {
return (
<>
<h1>Dashboard</h1>
</>
)
}
export async function getStaticProps() {
return {
props: {
auth: true
},
}
}
So back in _app.js when the pages are getting rendered the getStaticProps will, as said docs say:
Next.js will pre-render this page at build time using the props
returned by getStaticProps.
So when pageProps.auth && !user is reached in _app, that is where auth comes from.
Last two things:
You need this useEffect function in the MyApp component with the user prop from the hook in its dependency. Because that will keep the URL in sync/correct, between the redirects.
In /pages/_app, MyApp add:
useEffect(() => {
router.replace(router.pathname, undefined, { shallow: true })
}, [user]);
In the AuthLogin component add:
useEffect(() => {
router.replace('/login', undefined, { shallow: true })
}, []);
This ensures when component gets rendered, the URL would be right.
I am sure if your page is changing frequently you'll have to look into getServerSideProps but for this solved my use case for static pages!
Thanks Juliomalves and Adrian!
I have created hello component, I fetched API data in hello component checked browser http://localhost:3000/hello working fine, I want import inside of index.js component now I checking http://localhost:3000/. but data not coming what issues. could you please solve this issue please below my code
hello.js:
function Hello({ posts }) {
console.log(posts)
return (
<>
<ul>
{posts.map((post) =>(
<p>{post.name}</p>
))}
</ul>
</>
)
}
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries. See the "Technical details" section.
export async function getServerSideProps() {
// Call an external API endpoint to get posts.
// You can use any data fetching library
const res = await fetch('https://jsonplaceholder.typicode.com/users')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Hello
index.js
import React from 'react';
// import Layout from "../components/layout";
// import NavBar from "../components/navbar";
import Hello from './hello'
function Index() {
return (
<>
<Hello />
</>
)
}
export default Index;
I am pretty sure that you can only use getServerSideProps in a page component, not in an embedded component. So the solution to this is to import it in index.js and then send the data down to hello.js through props.
EDIT:
here is the code -->
hello.js
function Hello(props) {
return (
<>
<ul>
{props.posts.map((post) =>(
<p>{post.name}</p>
))}
</ul>
</>
)
}
export default Hello
index.js
import React from 'react';
import Hello from './hello'
function Index({posts}) {
return (
<>
<Hello posts={posts}/>
</>
)
}
export async function getServerSideProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
const posts = await res.json()
return {
props: {
posts
},
}
}
export default Index;