Query values lost on page refresh in Next js? [Example given] - javascript

I am making a simple Next Js application which has only two pages..
index.tsx:
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
As per the above code, on click Go to the about page it goes to about page and using query I also receive the passed query values in about page.
about.tsx
import React from "react";
import Router, { withRouter } from "next/router";
function About({ router: { query } }: any) {
return (
<div>
Candidate Id: <b> {query.candidateId} </b>
</div>
);
}
export default withRouter(About);
This displays the value but on page refresh while we are in /about page, the candidateId received gets disappeared.
Requirement: Kindly help me to retain the query value passed down from one page to another page even on page refresh.
Note: As per my requirement I should not display the canidateId on url while navigating and hence I am using as approach.. I know I can achieve it if I remove as but I cannot remove that here in index page while navigating.. Reason is this will lead to displaying candidateId in the url which is not intended..
Tried this solution: https://stackoverflow.com/a/62974489/7785337 but this gives empty query object on refresh of page.
Stuck for very long time with this please kindly help me.

If you do not want to use the query parameter you may need to create a "store" that saves your variable that persist throughout your pages.
Sample code as follows.
//candidatestore.js
export const CandidateStoreContext = createContext()
export const useCandidateStore = () => {
const context = useContext(CandidateStoreContext)
if (!context) {
throw new Error(`useStore must be used within a CandidateStoreContext`)
}
return context
}
export const CandidateStoreProvider = ({ children }) => {
const [candidateId, setCandidateId] = useState(null);
return (
<CandidateStoreContext.Provider value={{ candidateId, setCandidateId }}>
{children}
</CandidateStoreContext.Provider >
)
}
Then you need to wrap the Provider around your app like
<CandidateStoreProvider><App /></CandidateStoreProvider>
This way you can use anywhere as follows both in your index page and your about page.
const { candidateId, setCandidateId } = useCandidateStore()
UseContext
In your codes, it should probably look something like that.
import React from "react";
import Link from "next/link";
import { useCandidateStore } from './candidatestore'
export default function Index() {
const { candidateId, setCandidateId } = useCandidateStore()
useEffect(() => {
setCandidateId(thecandidateId)
})
return (
<div>
<Link
href={{
pathname: "/about",
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
function About({ router: { query } }: any) {
const { candidateId, setCandidateId } = useCandidateStore()
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}

Update to Next.JS 10. It comes with Automatic Resolving of href which fixes your problem.

Try to delete the as="about" and then navigate again to the "about" page, the issue should be gone.
Codesandbox

My best bet would be to store the candidateId in an encrypted session on the client side. You could read/verify cookies in getServerSideProps() and pass their contents to the page component. If this sounds feasible, I'd recommend checking out the next-iron-session.
Another approach would be to check if candidateId exists in the query object in getServerSideProps(). If it does then pass it straight to the page component. If not, either get it elsewhere, redirect, or pass some default value. Append the following starter code to your about.tsx:
/* ... */
export function getServerSideProps({ query }: any) {
// if query object was received, return it as a router prop:
if (query.candidateId) {
return { props: { router: { query } } };
}
// obtain candidateId elsewhere, redirect or fallback to some default value:
/* ... */
return { props: { router: { query: { candidateId: 8432 } } } };
}

index.tsx file
Keep the code same as it is.
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
AboutUs.tsx
Code starts from here
Adding router as a dependency in the useEffect the issue should get solved.
import Router, { useRouter } from "next/router";
import React, { useState, useEffect } from 'react';
function About({ router: { query } }: any) {
const route = userRouter();
const [candidateId, setCandidateid] = useState();
useEffect(() => {
const {candidateId} = router.query
if(candidateId) {
setCandidateid(candidateid)
}},[router]) //Here goes the dependency
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
export default (About);

Related

How to use SSR data fetching in nextjs with apollo client?

I am try to use apollo-client with nextjs. Here I want to fetch data in getServerSideProps. Suppose I have 2 components and one page-
section.tsx this is component-1
const Section = () => {
return (
<div>
Section
</div>
);
};
export default Section;
mobile.tsx this is component 2
const Mobile = () => {
return (
<div>
Mobile
</div>
);
};
export default Mobile;
Now I call this two component into home page.
index.tsx-
const Home: NextPage = () => {
return (
<Container disableGutters maxWidth="xxl">
<Section />
<Mobile />
</Container>
);
};
export default Home;
export const getServerSideProps: GetServerSideProps = async () => {
const { data } = await client.query({ query: GET_USER_LIST })
return { props: {} }
}
Here you can see that in getServerSideProps I already fetch my data.
My question is How can I directly access this data form Section component and Mobile component without passing props. I don't want to pass props, because if my component tree will be more longer, then it will be difficult to manage props.
From appollo docs, I alreay know that apollo client do the same with redux state manager. So please tell me how can I access this data from any component that already fetched in getServerSideProps. Is it possible?. If not then how can what is the solutions.
How about using context api if you want to avoid prop drilling? By putting data into context, you can access it from any child component. Get the data from the SSR and put it into the context.
Below is the example
import React, {createContext, useContext} from "react";
export default function Home({data}) {
return <DataContext.Provider value={{data}}>
<div>
<Section/>
<Mobile/>
</div>
</DataContext.Provider>
}
export async function getServerSideProps() {
const data = 'hello world' //Get from api
return {
props: {data},
}
}
function Section() {
return <div>
Section
</div>
}
function Mobile() {
const context = useContext(DataContext);
return <div>
Mobile {context.data}
</div>
}
const DataContext = createContext({});
Now, as long as your tree structure grows within the DataContext provider, each child node will have access to data in the context.
Hope this helps.

How to get the current full URL in Next.js/Typescript component?

I have a component that I need to get the current full URL in, here's the simplied version:
/**
* Share dropdown component
*/
export const ShareDropdown: React.FC<{ className: string }> = ({
className,
}) => {
return (
<div className={className}>{currentURL}</div>
)
}
Did some Googling and saw some say use Next's router (but didn't see that return the domain), other using getInitialProps but didn't see how to incorporate into a functional component.
New to all of this, so just trying to find the best way, seems like it should be simple.
You can use simply useRouter from nextjs just like this
import { useRouter } from 'next/router';
...
const { asPath } = useRouter();
...
return (
<div className={className}>http://example.com{asPath}</div>
)
Additionally you can save http://example.com in your environment variables
If you have configured a base path you need to get it
import { useRouter, basePath } from 'next/router';
More info about useRouter here
Store window.location.href as a variable. so, like: const URL = window.location.href and then simply put {URL} here:
/**
* Share dropdown component
*/
export const ShareDropdown: React.FC<{ className: string }> = ({
className,
}) => {
const URL = window.location.href
return (
<div className={className}>{URL}</div>
)
}

How to add Nextjs cookie based secure authentication?

I have a small issue. I'm very very very new to Nextjs and I'm trying to learn by making a app. I have managed to make a Login system using next and I have few issues when securing routes. I have successfully added a cookie after successful login. Now I want to validate the cookie whenever user go to a protected route. I have followed below steps using this tutorial.
Made a Higher order component and checked the cookie validation using it.
Wrap the protected component using it.
Below is my HOD.
import { useRouter } from "next/router";
import Cookies from 'js-cookie';
const withAuth = (WrappedComponent) => {
return (props) => {
if (typeof window !== "undefined") {
const Router = useRouter();
const accessToken = Cookies.get('token');
if (!accessToken) {
Router.replace("/");
return null;
}
return <WrappedComponent {...props} />;
}
return null;
};
};
export default withAuth;
And then I have wrapped my component using above HOD.
import React, { Component } from 'react';
import withAuth from '../utils/withAuth';
class Home extends Component {
render() {
return (
<div>
HOME
</div>
);
}
}
export default withAuth(Home);
ISSUE #1
Above HOD is showing a console warning saying below.
Warning: Expected server HTML to contain a matching in .
div
Is their anyway I can fix this issue? As per some github answer I have found this can be solved using useEffect. SOURCE
Can anyone help me with this?
ISSUE #2
In this way, I have to wrap each and every protected component with my HOD. Is this the correct way of doing this or is there any other way to do this better than this?
Thank you so much or your support.
After spending some time. I was able to fix the issue by using below code. Now I just want to know the answer for 2nd issue mentioned above.
ISSUE #2 In this way, I have to wrap each and every protected component with my HOD. Is this the correct way of doing this or is there any other way to do this better than this?
Thank you so much or your support.
Code I use to fix the issue
import Router from 'next/router'
import Cookies from 'js-cookie';
import React, { useState, useEffect } from 'react';
const withAuth = (WrappedComponent) => {
return (props) => {
const [isLoggedIn, setLoginStatus] = useState(false);
useEffect(() => {
if (typeof window !== "undefined") {
const accessToken = Cookies.get('token');
if (accessToken) {
setLoginStatus(true)
}
else {
Router.push("/")
}
}
}, []);
if (isLoggedIn) {
return <WrappedComponent {...props} />;
} else {
return null;
}
}
};
export default withAuth;

Data from Gatsby GraphQL always returns undefined?

Gatsby noob here so please bear with me. I have a component that accepts props from the index.js where it is supposed to receive data from an array of objects but will always receive the error TypeError: Cannot read property 'map' of undefined where it's referring to the Hero.js component index.js is calling for.
My assumption is that the data being queried in index.js is either not specific enough or that it is rendering the component before data is received. Here is the index.js file:
import { graphql } from 'gatsby';
import { Layout, SEO, Hero } from 'components';
const IndexPage = ({ data }) => {
const dataFetch = data.contentfulTemplateIndex.heroes;
let tester = () => {
for (let count = 0; count < dataFetch.length; count++) {
return <Hero {...props} />;
}
};
console.log(dataFetch);
let props = {
impactText: dataFetch.impactText,
labels: dataFetch.labels,
primaryLabel: dataFetch.primaryLabel,
location: dataFetch.location
// supportingText: dataFetch.supportingText.json
};
return (
<Layout>
{dataFetch && tester()}
</Layout>
);
};
export const query = graphql`
query {
contentfulTemplateIndex {
heroes {
image {
fluid {
src
}
}
impactText
labels
location
primaryLabel
supportingText {
json
}
}
}
}
`;
export default IndexPage;
Here is the Hero.js component which index.js is calling:
import { Link } from 'gatsby';
import { documentToReactComponents } from '#contentful/rich-text-react-renderer';
import cx from 'classnames';
import styles from './hero.module.scss';
const Hero = (props) => {
return (
<div>
<ul>
<Link className={styles.pills}>{props.primaryLabel}</Link>
{props.labels.map((label) => {
return <Link className={styles.pills}>{label}</Link>;
})}
</ul>
<div className={styles.grid}>
<h1>{props.impactText}</h1>
</div>
</div>
);
};
export default Hero;
It's impossible for an outsider to debug your code without a minimum reproducable example.
The best way to debug GraphQL is to use the GraphiQL interface of your browser.
Run gatsby develop. If it fails because of the TypeError remove the lines of code that cause the type error (but not the code of your GraphQL query!). You need to get your development server runnning.
Open your browser, use the URL: http://localhost:8000/___graphql
Copy your graphQL query from your code and paste it into the GraphiQL query window.
Can you access your data there? If not you made a mistake writing your query or the data is not where it's supposed to be.
This way you can make sure the data exists.
It also helps to console.log(props) so you can examine the data object:
const Hero = (props) => {
console.log(props);
return (

How to pass info with React Router to the new page?

I've made a quick CodeSandBox example what I am after. I have a "Our Courses" section on the landing page with the button "Read more". Once the "Read more" button gets clicked, depending on the Course it would render that information. Now I got the button to work but now I am stuck and can't figure out how to pass relevant information to the redirected page. Now let's say I want to get the Course "Title" and "Description" get passed onto to the redirected page. How can I do that?
CodeSandBox link here - Link here
Your CardInfo component can look-up the course detail from your courses repository.
To perform the look-up you can determine which card was selected by using the react-router useParams hook; this allows you to determine which course identifier was passed via the selected route i.e.
import React from "react";
import courses from "./courses";
import { useParams } from "react-router-dom";
const CardInfo = () => {
const { id } = useParams();
const course = courses.find(course => course.id === id);
return (
<div>
<h1>{course.title}</h1>
<p>{course.description}</p>
</div>
);
};
export default CardInfo;
A complete working example of this can be seen here (its a fork of your CodeSandBox).
You can pass data between Routes by using the object version of the to prop of Link component, so change your Link component to this:
//Card.jsx
<Link
to={{
pathname: `/card/${course.title}`,
state: {
description: course.description
}
}}>
<button className="btn">Read more</button>
</Link>
Then in your CardInfo.jsx component you can access this data by props.location.state.description
import React from "react";
const CardInfo = (props) => {
console.log(props)
return (
<div>
<h1>
How can I pass course title here depending on which button I click
</h1>
<p>{props.location.state.description}</p>
</div>
);
};
export default CardInfo;
Hope it helps :)
In your CardInfo component you can access the id provided by the route using the useParams from your react-router-dom library.
I'm using your <Route path="/card/:id" component={CardInfo} /> for reference.
Implement it like this:
import React from 'react'
import { useParams } from 'react-router-dom'
const CardInfo = () => {
const { id } = useParams()
return <div>Card ID: {id}</div>
}
export default CardInfo
Now that you've got the id you should be able to use it for whatever you need.
There are multiple approaches to pass this data:
You can pass data through the link state like this:
<Link
to={{
pathname: `/card/${course.title}`,
state: { description: course.description }
}}
>...</Link>
And then read it in the CardInfo component like this:
import { useLocation } from "react-router-dom";
const CardInfo = () => {
const location = useLocation();
console.log(location.state) // { description: 'Lorem ipsum...' }
However, the best way to do this is to pass the course id in the URL and read the rest of the information from the courses.js file:
This is already correct, you accept the course id as URL paramter:
<Route path="/card/:id" component={CardInfo} />
Pass the course id in the link:
<Link to={`/card/${course.id}`}>
Read the id parameter from the URL and get the rest of the course information from the courses file:
import { useParams } from "react-router-dom";
import courses from './courses'
const CardInfo = () => {
const params = useParams();
console.log(courses[params.id]);

Categories