I am trying to set up a graphql frontend application with nextjs13 but with the new folder structure as there ins't _app.tsx I am wondering how to setup
Before we use to wrap the entire app like this
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
Now how to set up that?
What you want to do now is leverage the layout functionnality of the new nextjs 13 routing.
Your _app.js now becomes the root layout, a layout.js file that must be placed at the base of the app directory.
As any file in this directory, they will be treated as server component by default, which will prevent your Provider to work. Therefore, you need to specify "use client" in this file and feed it the provider that will then be shared by the whole application.
Example code below:
"use client";
import { ApolloProvider } from "#apollo/client";
type RootLayoutProps = {
children: React.ReactNode;
};
const RootLayout = ({ children }: RootLayoutProps) => {
return (
<html>
<body>
<ApolloProvider>{children}</ApolloProvider>
</body>
</html>
);
};
export default RootLayout
The accepted answer doesn't work when I have attempted it like that. As mentioned in the comments this:
TypeError: Cannot read properties of undefined (reading 'Symbol(APOLLO_CONTEXT)')
Is the resulting error.
I found doing this works:
// /apollo/ApolloProvider.js
"use client";
import { ApolloProvider as Provider } from "#apollo/client";
import { client } from "./client";
export const ApolloProvider = ({ children }) => (
<Provider client={client}>{children}</Provider>
);
// /app/Layout.js
import { ApolloProvider } from "apollo";
const RootLayout = ({ children }) => (
<ApolloProvider>{children}</ApolloProvider>
);
export default RootLayout;
Related
I am building a blog using Storyblok (v2) and Next.js (v13). I understand that NEXT_PUBLIC environment variables can be exposed to the browser, and I do not want my access token exposed. I have used the api folder in the past to accomplish this in component files, but I do not think that I can use that for the page files—unless I am wrong. I have read the documentation from Next.js, and I am confused as to how I can accomplish this in my _app.js file. I am still learning about how to properly hide these using Next.js, and I was successful in my previous attempt.
This is what I have tried so far after searching for answers. I have added my access token to a .env file in the root of my project. I did not use NEXT_PUBLIC. I left that off because in my other project where I was able to hide my environment variables from the browser, that is how I did it. I also added it to my next.config.js file based on the information that I found in my search. This is what that file looks like:
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
env: { ACCESS_TOKEN: process.env.ACCESS_TOKEN },
}
module.exports = nextConfig
My _app.js file looks like this:
import { storyblokInit, apiPlugin } from '#storyblok/react'
import '../styles/globals.css'
storyblokInit({
accessToken: process.env.ACCESS_TOKEN,
use: [apiPlugin],
});
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
Of course the above does not work. When I add {process.env.ACCESS_TOKEN} to any page, it is showing the token. I understand why this does not work based on the docs that I read, but I just wanted to try everything that I could before posting my question here.
Prior to doing these things, I also tried adding my variable to the .env file in the root of my project (not using the NEXT_PUBLIC prefix), and then in my _app.js file, I had the following:
import { storyblokInit, apiPlugin } from '#storyblok/react'
import '../styles/globals.css'
storyblokInit({
accessToken: token,
use: [apiPlugin],
});
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
function getStaticProps() {
return {
props: {
token: props.env.ACCESS_TOKEN
}
}
}
My next.config.js file wasn't touched, so it did not contain env in it. The error that I get is that token is not defined. This article is where I found the steps that I attempted.
I started using trial and error after that—mainly because I went down the Google rabbit hole and got super frustrated. This was my Hail Mary:
import { storyblokInit, apiPlugin } from '#storyblok/react'
import '../styles/globals.css'
function MyApp({ Component, pageProps, props }) {
storyblokInit({
accessToken: props.token,
use: [apiPlugin],
});
return <Component {...pageProps} />
}
export default MyApp
function getStaticProps() {
return {
props: {
token: props.env.ACCESS_TOKEN
}
}
}
The error that I get here is: Cannot read properties of undefined (reading 'token')
Is what I am trying to do even possible in the _app.js file? I am happy to read any other documentation that I may not have found in my search if that is a better response to my question.
In Nextjs 13 - experimental app directory, if I wanted to use useState on the root layout/page I must add ‘use client’ to the code, which effectively prevents all nested components from being server components.. how can I work around this so that I can use useState and still have server components. Thanks to any responders.
I don't know if this answers to your question (it's better to add some example code to help users understand your problem)
If you create a Server Component, and in that component you add your Client Component, it works fine. For example
ClientComponent.tsx
"use client";
import {useState} from 'react';
export default function ClientComponent() {
const [count, setCount] = useState(0);
return (
<>
<h1>Client Component</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
)
}
ServerComponent.tsx
async function getData(){
const res = await fetch('http://localhost:3000/api/hello');
return await res.json();
}
export default async function ServerComponent() {
const data = await getData()
return (
<>
<h1>Server Component</h1>
<p>{data.name}</p>
</>
)
}
Api hello.ts
export default async function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
Your page
import ClientComponent from "./ClientComponent";
import ServerComponent from "./ServerComponent";
export default function Page() {
return(<>
<ClientComponent/>
<ServerComponent/>
</>
)
}
In this example ServerComponent is rendered on the server, but ClientComponent on the client so it maintain interactivity
Hope this will help
When page is refreshed query is lost, disappears from react-query-devtools.
Before Next.js, I was using a react and react-router where I would pull a parameter from the router like this:
const { id } = useParams();
It worked then. With the help of the, Next.js Routing documentation
I have replaced useParams with:
import { usePZDetailData } from "../../hooks/usePZData";
import { useRouter } from "next/router";
const PZDetail = () => {
const router = useRouter();
const { id } = router.query;
const { } = usePZDetailData(id);
return <></>;
};
export default PZDetail;
Does not work on refresh. I found a similar topic, but manually using 'refetch' from react-query in useEffects doesn't seem like a good solution. How to do it then?
Edit
Referring to the comment, I am enclosing the rest of the code, the react-query hook. Together with the one already placed above, it forms a whole.
const fetchPZDetailData = (id) => {
return axiosInstance.get(`documents/pzs/${id}`);
};
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {});
};
Edit 2
I attach PZList page code with <Link> implementation
import Link from "next/link";
import React from "react";
import TableModel from "../../components/TableModel";
import { usePZSData } from "../../hooks/usePZData";
import { createColumnHelper } from "#tanstack/react-table";
type PZProps = {
id: number;
title: string;
entry_into_storage_date: string;
};
const index = () => {
const { data: PZS, isLoading } = usePZSData();
const columnHelper = createColumnHelper<PZProps>();
const columns = [
columnHelper.accessor("title", {
cell: (info) => (
<span>
<Link
href={`/pzs/${info.row.original.id}`}
>{`Dokument ${info.row.original.id}`}</Link>
</span>
),
header: "Tytuł",
}),
columnHelper.accessor("entry_into_storage_date", {
header: "Data wprowadzenia na stan ",
}),
];
return (
<div>
{isLoading ? (
"loading "
) : (
<TableModel data={PZS?.data} columns={columns} />
)}
</div>
);
};
export default index;
What you're experiencing is due to the Next.js' Automatic Static Optimization.
If getServerSideProps or getInitialProps is present in a page, Next.js
will switch to render the page on-demand, per-request (meaning
Server-Side Rendering).
If the above is not the case, Next.js will statically optimize your
page automatically by prerendering the page to static HTML.
During prerendering, the router's query object will be empty since we
do not have query information to provide during this phase. After
hydration, Next.js will trigger an update to your application to
provide the route parameters in the query object.
Since your page doesn't have getServerSideProps or getInitialProps, Next.js statically optimizes it automatically by prerendering it to static HTML. During this process the query string is an empty object, meaning in the first render router.query.id will be undefined. The query string value is only updated after hydration, triggering another render.
In your case, you can work around this by disabling the query if id is undefined. You can do so by passing the enabled option to the useQuery call.
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {
enabled: id
});
};
This will prevent making the request to the API if id is not defined during first render, and will make the request once its value is known after hydration.
I'm building a simple Next.js website that consumes the spacex graphql API, using apollo as a client. I'm trying to make an api call, save the returned data to state and then set that state as context.
Before I save the data to state however, I wanted to check that my context provider was actually providing context to the app, so I simply passed the string 'test' as context.
However, up[on trying to extract this context in antoher component, I got the following error:
Error: The default export is not a React Component in page: "/"
My project is set up as follows, and I'm thinking I may have put the context file in the wrong place:
pages
-api
-items
-_app.js
-index.js
public
styles
next.config.js
spacexContext.js
Here's the rest of my app:
spaceContext.js
import { useState,useEffect,createContext } from 'react'
import { ApolloClient, InMemoryCache, gql } from "#apollo/client"
export const LaunchContext = createContext()
export const getStaticProps = async () => {
const client = new ApolloClient({
uri: 'https://api.spacex.land/graphql/',
cache: new InMemoryCache()
})
const { data } = await client.query({
query: gql`
query GetLaunches {
launchesPast(limit: 10) {
id
mission_name
launch_date_local
launch_site {
site_name_long
}
links {
article_link
video_link
mission_patch
}
rocket {
rocket_name
}
}
}
`
});
return {
props: {
launches: data.launchesPast
}
}
}
const LaunchContextProvider = (props) => {
return(
<LaunchContext.Provider value = 'test'>
{props.children}
</LaunchContext.Provider>
)
}
export default LaunchContextProvider
_app.js
import LaunchContextProvider from '../spacexContext'
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return (
<LaunchContextProvider>
<Component {...pageProps} />
</LaunchContextProvider>
)
}
export default MyApp
Any suggestions on why this error is appearing and how to fix it?
This page is the most relevant information I can find but it isn't enough.
I have a generic component that displays an appbar for my site. This appbar displays a user avatar that comes from a separate API which I store in the users session. My problem is that anytime I change pages through next/link the avatar disappears unless I implement getServerSideProps on every single page of my application to access the session which seems wasteful.
I have found that I can implement getInitialProps in _app.js like so to gather information
MyApp.getInitialProps = async ({ Component, ctx }) => {
await applySession(ctx.req, ctx.res);
if(!ctx.req.session.hasOwnProperty('user')) {
return {
user: {
avatar: null,
username: null
}
}
}
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return {
user: {
avatar: `https://cdn.discordapp.com/avatars/${ctx.req.session.user.id}/${ctx.req.session.user.avatar}`,
username: ctx.req.session.user.username
},
pageProps
}
}
I think what's happening is this is being called client side on page changes where the session of course doesn't exist which results in nothing being sent to props and the avatar not being displayed. I thought that maybe I could solve this with local storage if I can differentiate when this is being called on the server or client side but I want to know if there are more elegant solutions.
I managed to solve this by creating a state in my _app.js and then setting the state in a useEffect like this
function MyApp({ Component, pageProps, user }) {
const [userInfo, setUserInfo] = React.useState({});
React.useEffect(() => {
if(user.avatar) {
setUserInfo(user);
}
});
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<NavDrawer user={userInfo} />
<Component {...pageProps} />
</ThemeProvider>
);
}
Now the user variable is only set once and it's sent to my NavDrawer bar on page changes as well.
My solution for this using getServerSideProps() in _app.tsx:
// _app.tsx:
export type AppContextType = {
navigation: NavigationParentCollection
}
export const AppContext = createContext<AppContextType>(null)
function App({ Component, pageProps, navigation }) {
const appData = { navigation }
return (
<>
<AppContext.Provider value={appData}>
<Layout>
<Component {...pageProps} />
</Layout>
</AppContext.Provider>
</>
)
}
App.getInitialProps = async function () {
// Fetch the data and pass it into the App
return {
navigation: await getNavigation()
}
}
export default App
Then anywhere inside the app:
const { navigation } = useContext(AppContext)
To learn more about useContext check out the React docs here.