Implement getSession() in NextJS 13 - next.js

I implemented nextAuth in my app and faced a glitch problem on UI, when the page is reloaded I can see Signed in as ../ Not signed in for a second till a new session is fetched. I found the solution of this problem for NextJS 12 and older, and I have some difficulties to implement it in NextJS 13 without getServerSideProps().
'use client'
import './globals.css'
import { getSession, SessionProvider } from 'next-auth/react'
export default function RootLayout({ session, children }) {
return (
<html lang="en">
<head />
<body>
<SessionProvider session={session}>
{children}
</SessionProvider>
</body>
</html>
)
}
How to implement this function for the code above?
export async function getServerSideProps(ctx) {
return {
props: {
session: await getSession(ctx)
}
}
}
Source: https://stackoverflow.com/a/68942471/4655668

getServerSession : When calling from server-side i.e. in API routes or in getServerSideProps, we recommend using this function instead of getSession to retrieve the session object. This method is especially useful when you are using NextAuth.js with a database.
import { authOptions } from 'pages/api/auth/[...nextauth]'
import { getServerSession } from "next-auth/next"
export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions)
//...
}

Related

Using the context API in Next.js

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?

Handle 401 error in react-redux app using apisauce

The problem: i have many sagas that do not handle an 401 error in response status, and now i have to deal with it. I have apiservice based on apisause and i can write an response monitor with it to handle 401 error (like interceptors in axios). But i cant dispatch any action to store to reset user data, for example, because there is no store context in apiservice. How to use dispatch function in apiservice layer? Or use put() function in every saga when i recieve 401 response status is the only right way?
you can use refs for using navigation in 'apisauce' interceptors
this is my code and it works for me ;)
-- packages versions
#react-navigation/native: ^6.0.6
#react-navigation/native-stack: ^6.2.5
apisauce: ^2.1.1
react: 17.0.2
react-native: ^0.66.3
I have a main file for create apisauce
// file _api.js :
export const baseURL = 'APP_BASE_URL';
import { create } from 'apisauce'
import { setAPIInterceptors } from './interceptors';
const APIClient = create({ baseURL: baseURL })
setAPIInterceptors(APIClient)
and is file interceptors.js I'm watching on responses and manage them:
// file interceptors.js
import { logout } from "../redux/actions";
import { store } from '../redux/store';
import AsyncStorage from '#react-native-async-storage/async-storage';
export const setAPIInterceptors = (APIClient) => {
APIClient.addMonitor(monitor => {
// ...
// error Unauthorized
if(monitor.status === 401) {
store.dispatch(logout())
AsyncStorage.clear().then((res) => {
RootNavigation.navigate('login');
})
}
})
}
then I create another file and named to 'RootNavigation.js' and create a ref from react-native-navigation:
// file RootNavigation.js
import { createNavigationContainerRef } from '#react-navigation/native';
export const navigationRef = createNavigationContainerRef()
export function navigate(name, params) {
if (navigationRef.isReady()) {
navigationRef.replace(name, params);
}
}
// add other navigation functions that you need and export them
then you should to set some changes in you App.js file:
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
finally in anywhere you can call this function for use react native navigations
full focument is in here that explain how to Navigating without the navigation prop
Navigating without the navigation prop

Next JS fetch data once to display on all pages

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.

How to restrict access to pages in next.js using firebase auth?

I am working on a next.js app which uses firebase. I need to use firebase auth package to restrict access to pages. The with-firebase-authentication example doesn't show authentication for multiple pages.
import React from 'react';
import Router from 'next/router';
import { firebase } from '../../firebase';
import * as routes from '../../constants/routes';
const withAuthorization = (needsAuthorization) => (Component) => {
class WithAuthorization extends React.Component {
componentDidMount() {
firebase.auth.onAuthStateChanged(authUser => {
if (!authUser && needsAuthorization) {
Router.push(routes.SIGN_IN)
}
});
}
render() {
return (
<Component { ...this.props } />
);
}
}
return WithAuthorization;
}
export default withAuthorization;
This is a React Firebase Authentication example, but it should work with next.js as well.
The main idea is to create a Higher Order Component, which checks if the user is authenticated and wrap all pages around that:
import React from 'react';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
render() {
return <Component {...this.props} />;
}
}
return WithAuthentication;
};
export default withAuthentication;
You could override the _app.js and only return <Component {...pageProps} /> if the user is authenticated.
You could do something like this:
const withAuthorization = (needsAuthorization) => (Component) => {
class WithAuthorization extends React.Component {
state = { authenticated: null }
componentDidMount() {
firebase.auth.onAuthStateChanged(authUser => {
if (!authUser && needsAuthorization) {
Router.push(routes.SIGN_IN)
} else {
// authenticated
this.setState({ authenticated: true })
}
});
}
render() {
if (!this.state.authenticated) {
return 'Loading...'
}
return (
<Component { ...this.props } />
);
}
}
return WithAuthorization;
}
Best would be to handle this on the server.
Struggled with integrating firebase auth as well, ended up using the approach detailed in the with-iron-session example on nextjs: https://github.com/hajola/with-firebase-auth-iron-session
Hi after some research here there seems to be two ways of doing this. Either you alternate the initialization process of the page using Custom to include authentication there - in which case you can transfer the authentication state as prop to the next page - or you would ask for a new authentication state for each page load.

How to do server side render using next.js?

When I create a simple app, I found that next.js will automatically do the server side render.
But when I tried to fetch the data from backend, I found that server side won't get the data.
How to fetch the data from server side? So that I can do the server side render?
components/test.js
import React, { Component } from 'react';
class Test extends Component {
constructor(){
super();
this.state={
'test':''
}
}
setTest(){
axios.get(serverName+'/api/articles/GET/test').then(response=>{
let test;
test = response.data.test;
this.setState({test});
}).catch(function (error) {
console.log(error);
});
}
}
render() {
return (
<div>
{this.state.test}
</div>
);
}
}
export default Test;
backend is just like following:
function getTest(Request $request){
return response()->json(['test'=>'this is a test']);
}
Next.js uses getInitialProps that is executed on the server on initial load only.
From docs
For the initial page load, getInitialProps will execute on the server
only. getInitialProps will only be executed on the client when
navigating to a different route via the Link component or using the
routing APIs.
All other lifecycle methods/actions on React components (componentDidMount, onClick, onChange etc) are executed on the client side.
Example code
class Test extends Component {
static async getInitialProps() {
const response = await axios.get(serverName + '/api/articles/GET/test');
return { test: response.data.test }
}
render() {
return <div>{this.props.test}</div>;
}
}
export default Test;
Like below. I would recomend to use getInitialProps. This is recommended approach by next.js to get data at server.
import React from 'react'
export default class extends React.Component {
static async getInitialProps({ req }) {
axios.get(serverName+'/api/articles/GET/test').then(response=>{
let test;
test = response.data.test;
return { test }
}).catch(function (error) {
return { response }
});
}
render() {
return (
<div>
Hello World {this.props.test.name}
</div>
)
}
}

Resources