Can you send me some examples of the page which only renders once.
I want to see how others implement this feature.
Since I use next.js I tried saving not in localstorage but using cookies and it works but in prod on vercel I can see this page WHEN I don't have to, it shows it for <1s and then show the correct page.
UPD:
It works but when I how the website using vercel this happends:
First time
Shows the welcome page
I press the button "NEXT" and I go to the next page
If I go back in doesn't allow me and it returns to the page on step 2
Once I close the google tab and reopen my website I:
See the welcome page for <0.5s
I am transferred to the page where I supposed to at first.
Maybe this is has to do with cookie i use
put the implementation of this page:
const firstTime = cookies(props).firstLaunch;
useEffect(() => {
const expire = new Date(new Date().getTime() + 7776000000000);
if (firstTime === undefined) {
document.cookie = `firstLaunch=false; expires=` + expire;
}
alert(localStorage.firstLaunch);
}, [firstTime]);
if (firstTime === "false") return <LoadingPage title={""} to={"/map"} />;
return (
... <=== welcome page
async function getStaticProps(ctx) {
return {
props: {
firstLaunch: cookies(ctx).firstLaunch,
},
};
}
I think you have to use getServerSideProps as getStaticProps runs on build time and cookies aren't available then. It's a bit different in development mode - static pages are rerendered on every change, so it may have looked to work properly.
Try this:
export async function getServerSideProps() {
const parsedCookies = cookie.parse(context.req.headers.cookie);
if (!parsedCookies.firstLaunch) {
return {
redirect: {
permanent: false,
destination: '/first-launch',
},
}
}
}
This way you can redirect user on server side without any content flash (0.5s). Your firstLaunch page must be available under some route though.
import Link from "next/link";
import React, {useEffect} from "react";
import { ArrowIcon } from "../components/ui/icons";
import cookies from "next-cookies";
import { useRef } from "react";
function Home() {
const cookieRef = useRef<Boolean>(true);
useEffect(() => { <=== I use useRef to make this block run only once
if(cookieRef.current) {
const expire = new Date(new Date().getTime() + 7776000000000);
document.cookie = `firstLaunch=yey; expires=` + expire;
cookieRef.current = false;
}
}, [cookieRef]);
return (
....
);
}
export default Home;
export async function getServerSideProps(ctx) {
const tmpCookies = cookies(ctx)
if (tmpCookies.firstLaunch) { <=== if it is not my first time
return {
redirect: {
permanent: false,
destination: '/map', <=== redirect
},
}
}
return { <==== if it is my first time don't do anything
props: {}
}
}
Thank you Aitwar for the idea of using getServerSideProps with redirect
Related
I am trying to have login page with using react typescript which i am using state management with Mobx and also make refresh token method as follow, my problem is before token expires out, the refresh token cant be trigerred by token time ends up. my login method is in userstore.tsx like as:
login = async (creds: UserFormValues) => {
try {
const user = await agent.Account.login(creds);
store.commonStore.setToken(user.token);
this.startRefreshTokenTimer(user);
runInAction(() => this.user = user);
window.location.href = '/dashboard'
} catch (error) {
throw error;
}
}
login method works without any hesitation after that refresh token timer works and runs refreshtoken method as follow;
refreshToken = async () => {
this.stopRefreshTokenTimer();
try {
const user = await agent.Account.refreshToken();
runInAction(() => this.user = user);
store.commonStore.setToken(user.token);
this.startRefreshTokenTimer(user);
} catch (error) {
console.log(error);
}
}
private startRefreshTokenTimer(user: User) {
const jwtToken = JSON.parse(atob(user.token.split('.')[1]));
const expires = new Date(jwtToken.exp * 1000);
const timeout = expires.getTime() - Date.now();
alert(expires.getTime() - Date.now())
this.refreshTokenTimeout = setTimeout(this.refreshToken, timeout);
}
private stopRefreshTokenTimer() {
clearTimeout(this.refreshTokenTimeout);
}
after time is up, i was waiting, refreshtoken method was trigerred but it wouldnt be possible after i gave even 1 min short token expire time and waited 1 min for triggered itself. my root component App.tsx is also as follow;
import 'devextreme/dist/css/dx.softblue.css';
import { observer } from 'mobx-react-lite';
import { Suspense } from 'react'
import { Outlet } from 'react-router-dom'
import { I18nProvider } from '../_metronic/i18n/i18nProvider'
import { LayoutProvider, LayoutSplashScreen } from '../_metronic/layout/core'
import { MasterInit } from '../_metronic/layout/MasterInit'
const App = observer(() => {
return (
<Suspense fallback={<LayoutSplashScreen />}>
<I18nProvider>
<LayoutProvider>
<Outlet />
<MasterInit />
</LayoutProvider>
</I18nProvider>
</Suspense>
)
})
export { App }
I think i skipped some thing i have to do, if anybody helps and wants me to be clearer and extra details, i can give more information about the issue which i got stuck.
this problem is not related to mobx. i solve this problem in the first code above that i realize i used here window.location.href = '/dashboard' and when the component changes the javascript reloads. the prevent the issues is that it should use history.push('/dashboard'). i use history version 6 and this version is a bit different than the earlier version, to see this usage, click the link here.
i take note this due that this might be helpful context if anybody gets the kind of issue
I am trying to create a logic for my blog/:post page in Next.js but I cannot seem to figure out how.
The idea is to:
Fetch the url (using useRouter)
Call API (it is a headless CMS) to get the info of the post
Render the post
What I have right now is:
[other imports ...]
import { useEffect, useRef, useState } from "react";
import { useRouter } from 'next/router'
const apikey = process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY;
const butter = require('buttercms')(apikey);
function BlogPost(props) {
const router = useRouter()
const { slug } = router.query
const [blogPost, setBlogPost] = useState({})
// Function to the blog post
function fetchBlogPost() {
butter.post.retrieve(slug)
.then(response => {
const blogPostData = response.data.data
setBlogPost(blogPostData)
})
}
useEffect(() => {
// We need to add this if condition because the router wont grab the query in the first render
if(!router.isReady) return;
fetchBlogPost()
}, [router.isReady])
return (
<>
# Render post with the data fetched
</>
)
}
export default BlogPost;
But this is not rendering everything (the image is not being rendered for example). I believe it is because of the pre-render functionality that Next.js has. Also I have been reading about the getStaticProps and getStaticPaths but I am unsure on how to use them properly.
Any guidance will be welcome. Thanks!
If you're using next.js then you are on track with getStaticProps being your friend here!
Essentially getStaticProps allows you to take advantage of ISR to fetch data on the server and create a static file of your page with all of the content returned from the fetch.
To do this you'll need to make an adjustment to your current architecture which will mean that instead of the slug coming in from a query param it will be a path parameter like this: /blogs/:slug
Also this file will need to be called [slug].js and live in (most likely) a blogs directory in your pages folder.
Then the file will look something like this:
import { useEffect, useRef, useState } from "react";
import { useRouter } from 'next/router'
const apikey = process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY;
const butter = require('buttercms')(apikey);
export const getStaticPaths = async () => {
try {
// You can query for all blog posts here to build out the cached files during application build
return {
paths:[], // this would be all of the paths returned from your query above
fallback: true, // allows the component to render with a fallback (loading) state while the app creates a static file if there isn't one available.
}
} catch (err) {
return {
paths: [],
fallback: false,
}
}
}
export const getStaticProps = async ctx => {
try {
const { slug } = ctx.params || {}
const response = await butter.post.retrieve(slug)
if(!response.data?.data) throw new Error('No post data found') // This will cause a 404 for this slug
return {
notFound: false,
props: {
postData: response.data.data,
slug,
},
revalidate: 5, // determines how long till the cached static file is invalidated.
}
} catch (err) {
return {
notFound: true,
revalidate: 5,
}
}
}
function BlogPost(props) {
const {isFallback} = useRouter() // We can render a loading state while the server creates a new page (or returns a 404).
const {postData} = props
// NOTE: postData might be undefined if isFallback is true
return (
<>
# Render post with the data fetched
</>
)
}
export default BlogPost;
In any case, though if you decide to continue with rendering on the client instead then you might want to consider moving your fetch logic inside of the useEffect.
I have a small dummy react application, I have created a "Home Page" after the user is logged on the home page context is set for user credentials from the home page and I am able to get it on the home page. but I have created another page "profile" after I change the route to profile in URL, there is no longer data. I understand that since the page is refreshed data is lost because data persistence is the job of databases. but if I have to query some common data for every page what advantage does context put on the table? thanks.
On Home Page below the code, I wrote to set user credentials.
Home.js
**const { userCredentails, setUserCredentails } = useUserContext();**
useEffect(() => {
const getInitialData = async () => {
const returnData = await AuthFunction();
if (returnData.success) {
**setUserCredentails(returnData.user);**
return dispatch({ type: true, userData: returnData.user });
}
return dispatch({ type: false });
};
getInitialData();
}, []);
Now if a want to access the same data on the profile I don't want to query the database how do get it from there.
**const cntx = useContext(userCredentialsContext);**
above code returns empty objects.
finally, this is the context.js page
import { useState, createContext, useContext } from "react";
export const userCredentialsContext = createContext({});
export const UserContextProvider = ({ children }) => {
const [userCredentails, setUserCredentails] = useState({});
return (
<userCredentialsContext.Provider
value={{ userCredentails, setUserCredentails }}
>
{children}
</userCredentialsContext.Provider>
);
};
export const useUserContext = () => {
const data = useContext(userCredentialsContext);
return data;
};
if you implement your routing by using an anchor tag() or window.location, then the whole page will refresh including your context, setting it back to default state, the right way to do it is by using the Link tag from react-router-dom, here is an example;
import { Link } from "react-router-dom";
<Link to="/home">Home</Link>
I'm using supabase and trying to load the user session on the server side. If you refresh the page, it catches there is a user but not on first load (e.g. like when coming from a magic link). How can I ensure it does load before he page?
List item
Here is the page:
import router from "next/router";
import { supabase } from "../utils/supabaseClient";
function Home() {
const user = supabase.auth.user()
if (user){
//router.push('/admin') failsafe, not ideal
}
return (
<div className="min-h-screen bg-elkblue dark:bg-dark-pri">
marketing
</div>
);
}
export async function getServerSideProps({ req }) {
const { user } = await supabase.auth.api.getUserByCookie(req);
if (user) {
return {
redirect: {
destination: "/admin",
permanent: false,
},
};
}
return {
props: { }, // will be passed to the page component as props
};
}
export default Home;
You can use the auth-helpers for help with server-side rendering https://github.com/supabase-community/supabase-auth-helpers/blob/main/src/nextjs/README.md#server-side-rendering-ssr---withpageauth
Do note that it however needs to render the client first after OAuth because the server can't access the token from the URL fragment. The client will then read the token from the fragment and forward it to the server to set a cookie which can then be used for SSR.
You can see an example of that in action here: https://github.com/vercel/nextjs-subscription-payments/blob/main/pages/signin.tsx#L58-L62
I'm having a website where I show different kinds of projects to the user. I'm using Hasura, NextJS and Apollo to make this happen. The problem I have now is that every time I come back to the home page (where I show my projects) the query is getting fired again and has to load all over again. I want to save the data in my cache, but I'm not sure how to do that.
This is how I do it now:
I'm calling the query, when its done loading, I return my Home component with the data.
const ProjectList = () => {
const { loading, error, data } = useQuery(GET_PROJECTS);
if (loading) {
return <div>Loading...</div>;
}
if (error) {
console.error(error);
return <div>Error!</div>;
}
return <Home projects={data.projects} />;
};
export default withApollo({ ssr: true })(ProjectList);
In the home component I know can use the data:
const Home = ({ projects }) => {
// rest code...
}
I create my apolloClient as the following:
export default function createApolloClient(initialState, headers) {
const ssrMode = typeof window === 'undefined';
let link;
if (ssrMode) {
link = createHttpLink(headers); // executed on server
} else {
link = createWSLink(); // executed on client
}
return new ApolloClient({
ssrMode,
link,
cache: new InMemoryCache().restore(initialState),
});
}
Is there a way I can save the data in the cache, so I don't have to wait for the loading state every time I come back to the home page?
You can use the prebuild feature of nextjs. For this you need to call getStaticProps in your ProjectList.jsx file. There you have to import your apollo client as well as your query, then initialize the client and do the query:
export async function getStaticProps() {
const apollo = require("../../../apollo/apolloClient"); // import client
const GET_PROJECTS = require("../../../apollo/graphql").GET_PROJECTS; // import query
const client = apollo.initializeApollo(); //initialize client
const { data, error } = await client.query({
query: GET_PROJECTS
});
if (!data || error) {
return {
notFound: true
};
}
return {
props: { projects: data.projects } // will be passed to the page component as props
};
}