I try to fetch data (slugs of categories) from my server and use it inside the header navbar that is reusable in all the site.
In _app I have the layout which contains the header component:
function MyApp({ Component, appProps, pageProps, router }) {
return (
<>
<SlugContext.Provider value={{ slugs: pageProps.response }}>
<Layout>
<Component {...pageProps} />
</Layout>
</SlugContext.Provider>
</>
);
}
MyApp.getInitialProps = async ctx => {
const response = await axios
.get(`${API}/categories/`)
.then(res => {
return res.data;
})
.catch(error => {
if (error) {
console.log(error);
}
});
const pageProps = await App.getInitialProps({ response, ...ctx });
return { pageProps: { ...pageProps, response } };
};
In the header navbar I put the context:
const context = useContext(SlugContext);
and inside I have links like:
<NavLink href={`/categories/${context.slugs[0].slug}`} onClick={closeMobileMenu}>
<NavLink href={`/categories/${context.slugs[1].slug}`} onClick={closeMobileMenu}>
THE PROBLEM: When I console log inside the header I can see my slugs. When I click on one link -> it directs me to the slug, like: http://localhost:3000/categories/slugone
Bu the following message appears:
Unhandled Runtime Error
TypeError: Cannot read property '0' of undefined
...........
href={`/categories/${context.slugs[0].slug}`}===undefined
Why is it undefined with 0, if before he have all the slugs data? It has to be in the context, why does it disappear and how can I handle it?
Related
I'm working with NextJS, Next-auth and Django as backend. I'm using the credentials provider to authenticate users. Users are authenticated against the Django backend and the user info together with the accesstoken is stored in the session.
I'm trying to use useSWR now to fetch data from the backend. (no preloading for this page required, that's why I'm working with SWR) I need to send the access_token from the session in the fetcher method from useSWR. However I don't know how to use useSWR after the session is authenticated. Maybe I need another approach here.
I tried to wait for the session to be authenticated and then afterwards send the request with useSWR, but I get this error: **Error: Rendered more hooks than during the previous render.
**
Could anybody help with a better approach to handle this? What I basically need is to make sure an accesstoken, which I received from a custom backend is included in every request in the Authorization Header. I tried to find something in the documentation of NextJS, Next-Auth or SWR, but I only found ways to store a custom access_token in the session, but not how to include it in the Header of following backend requests.
This is the code of the component:
import { useSession } from "next-auth/react";
import useSWR from 'swr';
import axios from 'axios'
export default function Profile() {
const { data: session, status } = useSession();
// if session is authenticated then fetch data
if (status == "authenticated") {
// create config with access_token for fetcher method
const config = {
headers: { Authorization: `Bearer ${session.access_token}` }
};
const url = "http://mybackend.com/user/"
const fetcher = url => axios.get(url, config).then(res => res.data)
const { data, error } = useSWR(url, fetcher)
}
if (status == "loading") {
return (
<>
<span>Loading...</span>
</>
)
} else {
return (
<>
{data.email}
</>
)
}
}
you don't need to check status every time. what you need to do is to add this function to your app.js file
function Auth({ children }) {
const router = useRouter();
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push("/sign-in");
},
});
if (status === "loading") {
return (
<div> Loading... </div>
);
}
return children;
}
then add auth proprety to every page that requires a session
Page.auth = {};
finally update your const App like this
<SessionProvider session={pageProps.session}>
<Layout>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</Layout>
</SessionProvider>
so every page that has .auth will be wrapped with the auth component and this will do the work for it
now get rid of all those if statments checking if session is defined since you page will be rendered only if the session is here
Thanks to #Ahmed Sbai I was able to make it work. The component now looks like this:
import { useSession } from "next-auth/react";
import axios from "axios";
import useSWR from 'swr';
Profile.auth = {}
export default function Profile() {
const { data: session, status } = useSession();
// create config with access_token for fetcher method
const config = {
headers: { Authorization: `Bearer ${session.access_token}` }
};
const url = "http://mybackend.com/user/"
const fetcher = url => axios.get(url, config).then(res => res.data)
const { data, error } = useSWR(url, fetcher)
if (data) {
return (
<>
<span>{data.email}</span>
</>
)
} else {
return (
<>
Loading...
</>
)
}
}
App component and function:
function Auth({ children }) {
const router = useRouter();
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push("/api/auth/signin");
},
});
if (status === "loading") {
return (
<div> Loading... </div>
);
}
return children;
}
function MyApp({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={pageProps.session}>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</SessionProvider>
)
}
I am using next redux wrapper in App.getInitialProps but in client response I get duplicated initialState are: initialState outside pageProps and inside pageProps, this happens everytime when navigate to another page. How can I fix it?
You can see the image below:
My code below:
function App({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
App.getInitialProps = wrapper.getInitialPageProps((store) => async (context) => {
if (context.req) {
const dataAuth = await fetchAuth(context);
// dispatch action auth to update data initialState
// this only running on the server-side
if (dataAuth) store.dispatch(authSlice.actions.setAuth(dataAuth));
else store.dispatch(authSlice.actions.resetAuth());
}
});
I have global styles defined in my nextjs application inside /styles/globals.css and imported in _app.tsx
// import default style
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;
and page in in pages directory (pages/index.tsx)
export async function getServerSideProps({req, res }) {
// Some fetch here
return {
props: {
someProps: objectFromFetchThere,
},
};
}
const Home: NextPage<{ someProps: SomeProps[] }> = ({ someProps }) => {
return (
<>
<Head>
<title>Home page</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Layout>
// More code here
</Layout>
</>
);
};
When I'm running an application with next dev then everything is working fine (styles are loaded, getServerSideProps is called etc.), but when I'm running in production (next build && NODE_ENV=production ts-node src/server.ts) then global styles are not loaded (and _app file is also not used). Does it mean that I can't use global styles in pages with getServerSideProps exported? I didn't find anything related to that in NextJS documentation. Am I missing something here?
My custom server:
(async () => {
try {
const expressServer = express();
await app.prepare();
// Some custom routes defined over there like /facility - nothing that can overlap with styles.
// Error middleware has to be used after other custom routes
expressServer.use(
(err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).send("Unexpected error occurred.");
}
);
expressServer.all("*", async (req: Request, res: Response) => {
try {
await handle(req, res);
} catch (e) {
console.error("Error occurred handling", req.url, e);
res.statusCode = 500;
res.end("internal server error");
}
});
expressServer.listen(port, (err?: any) => {
if (err) throw err;
console.log(
`> Ready on localhost:${port} - env ${process.env.NODE_ENV}`
);
});
} catch (e) {
console.error(e);
process.exit(1);
}
})();
This issue is related to the custom server and typescript used at the same time. Changing "target": "es5" to "target": "es6" fixed the issue. A solution is taken from https://stackoverflow.com/a/67520930/7932025.
I have a issue with asyncData() when i refresh the page. If I navigate from list to single item, it work, but if i reload the page i will see an empty object.
In my page i have this :
<template>
<div>
{{ getItem}}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data: () => ({
}),
computed: {
...mapState([
'single_item'
]),
getItem() {
return this.single_item
}
},
async asyncData({app,route, params,store}) {
let type = 'posts'
let id = params.id
return store.dispatch('fetchFirebaseSingle', {type,id })
}
}
</script>
in store.js
import { db } from '~/plugins/firebase'
const actions = {
....
async fetchFirebaseSingle({commit}, {type, id}) {
try {
console.log('fetchFirebaseSingle', type)
const docRef = await db.collection(type).doc(id)
docRef.get()
.then((doc) => {
if (doc.exists) {
const file = doc.data()
commit('SET_PAGE_SINGLE', file)
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
})
.catch((error) => {
console.log("Error getting document:", error);
});
} catch (e) {
console.log("Error getting document:", e);
}
},
}
const mutations = {
...
// Set Single Item
SET_PAGE_SINGLE ( state, single_item) {
state.single_item = single_item
},
},
const state = () => ({
single_item : {},
})
I tryed also to call directly from this page the database, but i have same issue. Did someone get similar issue with vuex and firebase or asyncData ?
Thanks
Nothing special here, asyncData is not supposed to work on page reload or a refesh (F5) but only with page transitions.
Unlike fetch, the promise returned by the asyncData hook is resolved during route transition
You could use the fetch() hook if you don't mind a non-blocking loading.
More info here: https://nuxtjs.org/docs/features/data-fetching#data-fetching
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.