How to get global.window.localStorage inside getServerSideProps - javascript

I have some component which get props with data and render it data. In my getServerSideProps function I need to get data from localStorage, but I can't do it because of window is undefined.
I tried using if (typeof window !== 'undefined') but it's still not working. What's wrong with my code?
const MainComponent = ({serverResponseData}) => {
// ... some code
console.log(serverResponseData))
// ... some code
}
export async function getServerSideProps() {
let filterId = []
if (typeof window !== 'undefined') {
filterId = global.window?.localStorage.getItem('filterId') || '';
}
// ...some code, and filterId variable still empty array
const scoresRes = await fetchData('scores',{filterId});
return {
props: {scoresRes}
};
}
I'm also tried use useEffect, but got error
React Hook "useEffect" is called in function "getServerSideProps" that
is neither a React function component nor a custom React Hook
function.

Referring to the documentation
If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps.
Localstorage is only available on the client side and you are trying to access it in a server side only function , you can use something like
if (typeof window !== 'undefined') {
// your code
const id = query.id;
const getData = JSON.parse(localStorage.getItem("form"));
console.log(getData)
}
Please review this article to get more information on running client side only code.
Another approach would be to use a dynamic import where the hello3 component would contain the code accessing local storage.
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home

getServerSideProps only trigger on the server-side and it won't be available on the client-side
getServerSideProps only runs on server-side and never runs on the browser.
If you want to have that storage logic, I'd suggest you use cookies instead. Not like localStorage, cookies are available on both server-side and client-side.
You can modify your logic like below with cookies
export async function getServerSideProps(context) {
const filterId = context.req.cookies.filterId || ''; //get filterId from cookies
// ...some code, and filterId variable still empty array
const scoresRes = await fetchData('scores',{filterId});
return {
props: {scoresRes}
};
}
Another possible approach is using getInitialProps which is available on both sides
MainComponent.getInitialProps() {
let filterId = []
if (typeof window !== 'undefined') {
filterId = global.window?.localStorage.getItem('filterId') || '';
}
// ...some code, and filterId variable still empty array
const scoresRes = await fetchData('scores',{filterId});
return scoresRes;
}

Related

How do I get my layout component to remain static in Next13 app folder

I am trying to create a layout component that fetches its own data, I have tried adding the cache: 'force-cache' to the fetch but every time I update my CMS content and refresh my page the new content is loaded. Here is an example of my code:
const getLayoutData = async () => {
const response = await fetch(
`https://cdn.contentful.com/spaces/${
process.env.CONTENTFUL_SPACE_ID
}/environments/${
process.env.CONTENTFUL_ENVIRONMENT || "master"
}/entries/${fieldId}?access_token=${process.env.CONTENTFUL_ACCESS_TOKEN}`,
{
cache: "force-cache",
}
);
const {entryTitle, ...headerData} = await response.json();
return { headerData };
}
export default async function Layout() {
const data = await getLayoutData();
...
You can use the getStaticProps() function to fetch data at build time and make it available to your component as a prop. This way, the data will be pre-rendered on the server and will not change when the user refreshes the page:
import getLayoutData from './getLayoutData';
export async function getStaticProps() {
const data = await getLayoutData();
return { props: { data } };
}
export default function Layout({ data }) {
// Use data in your component
...
}
Alternatively you could use getServerSideProps(), it runs on the server at request time instead of build time. I would recommend that if you have dynamic data that changes frequently:
import getLayoutData from './getLayoutData';
export async function getServerSideProps(context) {
const data = await getLayoutData();
return { props: { data } };
}
export default function Layout({ data }) {
// Use data in your component
...
}
By default, Next.js automatically does static fetches. This means that the data will be fetched at build time, cached, and reused on each request. As a developer, you have control over how the static data is cached and revalidated.
Refer to the docs - https://beta.nextjs.org/docs/data-fetching/fundamentals
Also, this will work in production mode. So, make sure you are using next build && next start and not next dev.
In case you are fetching data from same URL anywhere else, the cache might be getting updated. As Next.js also does request deduplication built into the fetch function itself.

How to access a component function from main.js in Vue 3 with Composition API

Using Vue 3 and composition API I have a component that have this function:
const retrieveSignedJWT = async (callback) => {
if (jwtUrl.value && !callback) {
//console.log("There's no callback use the by default URL")
await fetch(jwtUrl.value)
.then(async (response) => {
const data = await response.text();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
let jwt = data;
token.value = data;
decodeToken(jwt);
retrieveCategories();
})
.catch((error) => {
errorMessage.value = error;
console.error("There was an error!", error);
});
} else {
//Function has a callback
token.value = callback;
}
};
What I need to do is to find a way to expose the previous component function so I can call it from the main.js. The scenario is that I'm creating an IIFFE with Vue 3 and Vite (a widget that the end user will load from a script) and hooking up a public function to it so the user can use it at any point in their code. That function can have or not have a callback that will expose a token implemented.
import { createApp } from "vue";
import "#/assets/styles/index.scss";
import App from "./App.vue";
import store from "./store";
let div = document.createElement("div");
document.body.appendChild(div);
div.setAttribute("id", "my-widget");
window.myWidget = {
load: function (endUserRetrievedJWT) {
if (endUserRetrievedJWT) {
const endUserJWT = endUserRetrievedJWT();
//Calling my component function w a call back
retrieveSignedJWT(endUserJWT);
} else {
//Calling my component function without a call back
retrieveSignedJWT();
}
},
};
createApp(App).use(store).mount("#my-widget");
So basically I'm trying to find a way to invoke the parent component function from the main.js file in order to manage how to save a token to the state of my application. That token can come from a default URL or in the shape of a callback function that the end-user will pass as an argument to the globally expose function coming from main.js.
main.js is meant for setting up your Vue application. Don't use this for any other code! It won't work.
Simply create a separate .js file (e.g. utils.js) and export the function from there.
I'm not completely sure what you're trying to achieve exactly but it looks like an authentication process for logging in. What you're probably trying to do is call the login/logout functions from within a Vue component? This could be as simple as
// Separate file, e.g. `authentication.js`
export const login = (cb) => {
// do not name the callback function (cb) 'callback' as that may get you unexpected behavior
// Your code
somePromise().then((res) => {
cb(res);
});
}
// Your component
import { login } from 'path/to/authentication';
login(myCallback);
const myCallback = (res) => {
...
};
I finally ended up exposing the function in the mounted hook like this:
onMounted(() => {
window.myWidget = {
load: retrieveEndUserJWT,
};
retrieveDataAttributes();
callWebsocket(websocket.value);
});
Then inside the same component, I create a method that will process the callback:
const retrieveEndUserJWT = async (endUserRetrievingTokenCallback) => {
const jwt = await endUserRetrievingTokenCallback();
processingToken(jwt);
};
And in the processingToken method, I deal with that token coming from the end-user callback function. I still have to navigate the pros and cons of exposing the function in the mounted hook.

React : make backend API call when user is authenticated, use LocalStorage if not

I'm working on a small note taking app with React and Node.js (Express). If the user is authenticated I make API calls to the backend to fetch, create, update, delete notes persisted in a MongoDB database. If he's not, the notes are stored in localStorage. I have an AuthContext with login, logout and signup functions.
I can know if the user is loggedIn with my useAuth() custom hook in my AuthContext :
const { user } = useAuth();
And I have a separate file to make the API calls that I use in my components (getNotes, createNotes ...)
I fetch my notes in the useEffect hook
React.useEffect(() => {
const notes: Note[] = getNotes();
setNotes(notes);
}, []);
And I render my notes like this (simplified)
{notes.length > 0 && (
<ul>{notes.map(renderNote)}</ul>
)}
const renderNote = (note) => {
return (
<Note note={note} />
);
};
My question is what would be a good practice to implement the different behaviors (API calls or localStorage) ?
I can add a parameter isLoggedIn to the functions and add an if statement inside the function like this (simplified version) :
const getNotes = (isLoggedIn) => {
if (isLoggedIn) {
return notes = fetch("/notes")
} else {
return notes = localStorage.getItem("notes")
}
}
But this does not look like something clean to do if I have do to this in every function.
Thanks in advance for your help
Here's something you could do. I think I'd suggest you create the idea of some store that implements a simple getter/setter interface, then have your useAuth hook return the correct store depending on the auth state. If authenticated, then your hook returns the remote store. If not, then it returns the local storage store. But your store looks the same to your component no matter whether it's a local or remote store.
Now your code can just call get/set on the store and not care about where your info is stored or even whether the user is logged in. A main goal is to avoid having a lot of if (loggedIn) { ... } code all over your app.
Something like...
const useLocalStorageStore = () => {
const get = (key) => {
return localStorage.getItem(key);
};
const set = (key, value) => {
// I append 'local' here just to make it obvious the
// local store is in use in this example
localStorage.setItem(key, `${value} local`);
};
return { get, set };
};
// This contrived example uses localStorage too to make my example easier,
// but you'd add the fetch business to your get/set methods
// here in this remote store.
const useRemoteStore = () => {
const baseUrl = "http://localhost/foo/bar";
const get = async (key) => {
//return fetch(`${baseUrl}/${key}`);
// really should fetch here, but for this example use local
return localStorage.getItem(key);
};
const set = async (key, value) => {
// I append 'remote' here just to make it obvious the
// remote store is in use in this example
localStorage.setItem(key, `${value} remote`);
};
return { get, set };
};
const useAuth = () => {
// AuthContext is your source of loggedIn info,
// however you have it available in your app.
const { login, logout, loggedIn } = React.useContext(AuthContext);
const authedStore = useRemoteStore();
const unauthedStore = useLocalStorageStore();
const store = loggedIn ? authedStore : unauthedStore;
return { login, logout, loggedIn, store };
};
At this point, the store has all you need to get or set key values. Then you can use it in your components with...
const MyComponent = () => {
const { loggedIn, login, logout, store } = useAuth();
const setNotesValue = async (value) => {
// Your store handles communicating with the correct back end.
await store.set("notes", value);
};
const getNotesValue = async () => {
// Your store handles communicating with the correct back end.
const value = await store.get("notes");
};
return (
<div>Your UI...</div>
);
};
Here's a sandbox to demo this: https://codesandbox.io/s/gallant-gould-v1qe0

Getting the Site URL in Next.JS [duplicate]

I want to get the page's full URL or site hostname like the image below on Static Site Generator.
I will try with window.location.hostname, but it doesn't work.
The error: window not defined.
If you want the hostname inside getInitialProps on server side, still you can get it from req
Home.getInitialProps = async(context) => {
const { req, query, res, asPath, pathname } = context;
if (req) {
let host = req.headers.host // will give you localhost:3000
}
}
With server-side rendering (getServerSideProps), you can use context.req.headers.host:
import type { GetServerSideProps, NextPage } from "next";
type Props = { host: string | null };
export const getServerSideProps: GetServerSideProps<Props> =
async context => ({ props: { host: context.req.headers.host || null } });
const Page: NextPage<Props> = ({ host }) => <p>Welcome to {host || "unknown host"}!</p>;
export default Page;
But with static generation (getStaticProps), the hostname is not available, because there is no request to get it from. In general, a server doesn't know its own public hostname, so you need to tell it. Using Next.js environment variables, put this in .env.local:
HOST=example.com
Then access it with process.env['HOST']:
import type { GetStaticProps } from "next";
export const getStaticProps: GetStaticProps<Props> =
async context => ({ props: { host: process.env['HOST'] || null }});
If you want to get the full URL:
import { useRouter } from 'next/router';
const { asPath } = useRouter();
const origin =
typeof window !== 'undefined' && window.location.origin
? window.location.origin
: '';
const URL = `${origin}${asPath}`;
console.log(URL);
The place where you are accessing the window make sure you add a check so that code is executed only on the browser and no during SSG"
if (typeof window !== 'undefined') {
const hostname = window.location.hostname;
}
Update:
If you have specified basePath in next.config.js:
module.exports = {
basePath: 'https://www.example.com/docs',
}
Then using useRouter, you can access the base path:
import { useRouter } from 'next/router'
function Component() {
const router = useRouter();
console.log({ basePath: router.basePath});
// { basePath: 'https://www.example.com/docs' }
...
}
But if you have a relative base path then you can use the first approach
Consider this package > next-absolute-url
import absoluteUrl from 'next-absolute-url'
const { origin } = absoluteUrl(req)
const apiURL = `${origin}/api/job.js`
If you deployed your Next.js app with now the apiURL will be something like https://your-app.now.sh/api/job.js.
However, if you are running the app locally the apiURL will be http://localhost:8000/api/job.js instead.
Using typeof window !== 'undefined' is the secure way. if (window) {} will run you into problems.
const hostname = typeof window !== 'undefined' && window.location.hostname ? window.location.hostname : '';
const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : '';
Using above code will give you the frontend/outside hostname/origin the client using: example.com, www.example.com, www.example.com:80 and so on, not the localhost stuff. useRouter() will return the server side hostname/origin (localhost, localhost:3000)
I believe you're better of doing this with a combination of useRouter and useEffect hooks. In my case I wanted to dynamically set the og:url of my webpage. This is what I did. We have router.pathname as a dependency so that ogUrl is updated every time we move to a different page.
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
const MyComponent = () => {
const router = useRouter();
const [ogUrl, setOgUrl] = useState("");
useEffect(() => {
const host = window.location.host;
const baseUrl = `https://${host}`;
setOgUrl(`${baseUrl}${router.pathname}`);
}, [router.pathname]);
return <div></div>
}
You need to ensure your access to window.location.hostname happens on the client-side only, and not during server-side rendering (where window does not exist). You can achieve that by moving it to a useEffect callback in your component.
function Component() {
useEffect(() => {
console.log(window.location.hostname)
console.log(window.location.href) // Logs `http://localhost:3000/blog/incididunt-ut-lobare-et-dolore`
}, [])
// Remaining code of the component
}
req.headers are Symbols and not Objects, so to get value, you use the get method
const host = req.headers.get("host"); // stackoverflow.com
AFAIK there are two ways of doing this:
Next JS provides us with the useRouter hook, first you have to import it in your component, then, to use the router object, you just have to declare it. For example:
const router = useRouter();
console.log(router.pathname);
const {pathname} = router; <---- To access the pathname directly.
Besides this, as #Xairoo said before, if you want to use the window object, you have to check if window !== 'undefined' to avoid errors. The window not defined error happens because Next JS use NodeJS to render the app and the window object is not defined in Node JS.
You can find a more detailed explanation in this link.
none oh the answers above solved the problem and this is the solution i figured it out :
function return_url(context) {
if (process.env.NODE_ENV === "production") {
// if you are hosting a http website use http instead of https
return `https://${context.req.rawHeaders[1]}`;
} else if (process.env.NODE_ENV !== "production") {
return "http://localhost:3000";
}
}
and on the getServerSideProps or getStaticProps functions you use
export async function getServerSideProps(context) {
let url = return_url(context);
const data = await fetch(`${url}/yourEndPoint`).then((res) => res.json());
return {
props: {
data: data,
},
};
}
Using a middleware.js file that you add to the root of your project can give you access to the host name and provide a lot of flexibility to perform actions based on it if needed.
https://nextjs.org/docs/advanced-features/middleware
// Example: redirecting a domain to a subdomain
import { NextResponse } from "next/server";
// This function can be marked `async` if using `await` inside
export function middleware(request) {
// Currently there is no main site so we redirect to the subdomain.
const host = request.headers.get("Host");
if (
process.env.NODE_ENV === "production" &&
host.startsWith("mydomain.com")
) {
return NextResponse.redirect(new URL("https://mysubdomain.mydomain.com"));
} else if (
process.env.NODE_ENV === "staging" &&
host.startsWith("staging.mydomain.com")
) {
return NextResponse.redirect(
new URL("https://mysubdomain-staging.mydomain.com")
);
}
}
in Next.js you can do like this,
by useEffect to get window.location.origin in client side,
and set it to state.
work fine in :
{
"next": "12.1.6",
"react": "18.1.0",
}
const Home: NextPage = () => {
const { asPath, query } = useRouter();
const [loading, setLoading] = useState(false);
const [loginCallBackURL, setLoginCallBackURL] = useState("");
useEffect(() => {
setLoginCallBackURL(
`${window.location.origin}/${query.redirect ? query.redirect : "user"}`,
);
}, []);
// if you do something like this, it can't get loginCallBackURL
// const loginCallBackURL = useMemo(() => {
// if (typeof window !== 'undefined') {
// return `${window.location.origin}/${
// query.redirect ? query.redirect : "user"
// }`;
// }
// return asPath;
// }, [asPath, query]);
return (
<div>
<Button
variant="contained"
href={queryString.stringifyUrl({
url: `${publicRuntimeConfig.API_HOST}/auth/google/login`,
query: {
callbackURL: loginCallBackURL,
},
})}
>
Sign in with google
</Button>
</div>
);
};
export default Home;
We can get current url like this:
import { useRouter } from 'next/router';
const router = useRouter();
const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : '';
const address_url = origin+router.asPath;

catch all URLs in [...slug] and only create static props for valid addresses in Next.js

I'm using dynamic routes and using fallback:true option to be able to accept newly created pages
first i check if parameters is true then i create related props and show the component with that props.
In console i can also see that next.js create new json file for that related page to not go to server again for the next requests to the same page.
But even I type wrong address next create new json file for that path. It means that next.js create json file for every wrong path request.
How can avoid that vulnerable approach?
export const getStaticProps: GetStaticProps = async ({ params }) => {
if (params.slug[0] === "validurl") {
const { products } = await fetcher(xx);
const { categories } = await fetcher(xx);
return { props: { categories, products } };
} else {
return { props: {} };
}
};
const Home = (props: any) => {
if (!props) {
return <div>404</div>;
}
return (
<MainLayout {...props}>
<FlowItems items={props.products} />
</MainLayout>
);
};
export async function getServerSideProps(context) {
console.log(context.params.slug);
...
You are in Server Side inside this getServerSideProps and passing the context lets you dynamically catch whatever value it takes for whatever request.
Then you can check the data you want to load like:
const slug = context.params.slug;
const data = await fetch(`${host}/endpoint/${slug.join('/')}`);
so the request will be like 'localhost:3000/endpoint/foo/slug/test
Then you can deal with those slugs and it's data in a backend logic (where it should be) in your endpoint (just to clarify this sort of logic it usually belongs to a gateway and not to an endpoint, this is just for educational purposes).
If the endpoint/gateway returns a 404 - Not found you can simply redirect to the 404.js page (which can be static), same for the rest of the possible errors available in your backend.

Categories

Resources