Use Next.js Dynamic routing query object in an SWR fetch request - javascript

When using Next.js dynamic routing I'm trying to make an SWR fetch request using the routing query object, but my SWR fetch request is being called before the query object is set.
Given the dynamic route /posts/[id], consider the page /posts/123.
import { useRouter } from 'next/router';
import useSWR from 'swr';
export default function MyPage() {
const router = useRouter();
const { id } = router.query;
const url = `https://my.api.com/posts/${id}` // I've also tried using let here
const { data, error } = useSWR(url, fetcher);
console.log(url)
This URL initially console logs as https://my.api.com/posts/undefined and an error is returned from the API because https://my.api.com/posts/undefined is, of course, not a valid path.
Immediately after, the URL console logs correctly as https://my.api.com/posts/123 and the data on the page usually then populates, but sometimes the data doesn't populate and it's stuck in a 'loading' state. When I hardcode the URL this never happens.
Is there something I'm doing wrong here? Why is the query object not available immediately? Is there a way to elegantly wait for it to be set?

You could try to use conditional data fetching like so:
const { data, error } = useSWR(id ? url : null, id ? fetcher : null);
Also check following conversation: https://github.com/vercel/next.js/discussions/15952

Related

fetch data by useEffect in a server side rendering page

I have a project with react js and next js. I am developing a dynamic page, with getStaticPaths and getStaticProps. So I am fetching most of the data in getStaticProps to make the page be rendered on server side.
But there are some data which I can't fetch on server side, because it needs token which is stored in local storage.
The question is, if I use useEffect hook to fetch those data on client side, does this all process make any advantage for SEO?
Or I have to change structures, and store token in cookies to fetch all data on server side?
Update:
I want to check if user is logged in, and based on the result, show the page in different styles. But no user-related data is going to be rendered.
Right now, my code looks like this:
export default function BookDetail(props) {
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
React.useEffect(() => {
// It captures token from cookies
const token = getCookie("token");
// Then I need to confirm that token is valid from backend
if (token) {
setIsLoggedIn(true);
}
}, []);
return (
<div>
{ !isLoggedIn ? (
{props.res.data.title}
<br/>
{props.res.data.description}
) : (
{props.res.data.title}
<br/>
<button type="button" onclick={()=>{window.location.href='http://example.com';}}
)}
</div>
);
}
If you need a token to fetch said data, that data is probably related to the user? Hence, doesn't and shouldnt be considered with SEO.
If your data is not specifically for the user. Consider making it accessable without token.
Edit based on the comments here:
Fetching data inside useEffect will absolutely affect SEO. You want to display part of a book (text) for users that are not logged in. You check if users are logged in by a request from the useEffect, this is fine and standard.
If you want to Google to be able to read your book-text with crawlers you can not fetch it in useEffect, I suggest the following:
in your getStaticProps: Fetch the data (book text) and pass it to your page. Display this information by default.
Then in your useEffect you check if the user is logged in. If they are --> remove the text and render a button instead.
This way, Google will read it as you intend, while logged in users will only see a button.
You can check no problem on the server side whether a user is logged in only when you use getServerSideProps - getStaticProps are executed at a built time so there is no communication with whatever user inputs into the UI simply because thats a different time frame: building the app on the server, only when the app is built user can interact with it.
But getServerSideProps are not executed at a built time, yet there are still executed on the server side and since useEffect is a frontend API it won't work there. So there are two ways:
If you use NextAuth - you can use getServerSideProps and on the context object you have 'req' property and the property passed to the getSession function (you need to import that function) will tell you whether user has a session or not. Here is an example code snipet:
import { getSession } from "next-auth/react";
// some code here like your frontend component
export const getServerSideProps = async (context) => {
const { req, res } = context;
const session = await getSession({ req: req });
if (!session) {
return {
redirect: { destination: "/", permanent: false },
};
}
const email = session.user.email;
return {
props: { email: email, session },
};
};
Here is more on the subject from the official next docs:
https://nextjs.org/docs/authentication
If you don't use NextAuth I am sure you can attach your token to the context object like in the example above and read it in getServerSideProps except not use getSession as that is NextAuth API. haven't done it though.

Render HTML + Javascript from HTTP response

I am sending a HTTP GET request from a browser to an external API using React. I'm getting a response containing some HTML and Javascript which I would like to render in my browser.
Here's my code so far:
const url = getExternalEndpoint()
fetch(url)
.then(function(response) {
return response.text();
}).then(function(data) {
console.log(data);
})
Based on Retrieve data from a ReadableStream object?
So I can see the HTML in the console, but I'm not sure how to render it.
For context, the external server I'm sending the request to is an OpenID Connect server.
So, there is one library called react-html-parser
To install, use the following command
npm install react-html-parser
# or 
yarn add react-html-parser
Here, you can use the state to update the value from API.
import ReactHtmlParser from 'react-html-parser';
const [html_string, Sethtml_string] = useState('')
//set value in the html_string
<div> { ReactHtmlParser (html_string) } </div>

Access the url that user has entered from in getServerSideProps Next JS

I am currently trying to find the page that the user has came from to enter this current one in the server side props, im not sure if thats possible? or are there any ways that i could access that
TLDR: get the url which user is coming from and not where is going to, example:
coming from: http://localhost:6060/product/id/55555-62/
going to: http://localhost:6060/products/
I would like to access the id query, in the /products/ getServerSideProps
export const getServerSideProps = wrapper.getServerSideProps(store => async ({ req }) => {
console.log(req);
return {
props: {},
};
});
https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props#context-parameter
resolvedUrl: A normalized version of the request URL that strips the _next/data prefix for client transitions and includes original query values.
export const getServerSideProps = (ctx) => {...}
Inside ctx you can find params, query, resolvedUrl etc
Would the referrer enough for the thing you needed it for?
req.headers.referer
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer

Dynamic route and additional search params in url in NextJS

I'm trying to redirect a user with router.push(url); where an url is like the following: [:lang]/something/[...dynamicRouteParams]?searchParam=true.
There's an issue, that a user is redirected to a page with URL: [:lang]/something/[...dynamicRouteParams]?searchParam=true&lang=something&dynamicRouteParams=item1&dynamicRouteParams=item2.
How can I get rid of the search params related to route params?
Yes, you can do this with getServerSideProps or getStaticProps
export async function getServerSideProps({res, params }) {
//get data you need from API or params
const data = api or params
//now redirect to a link with params
res.setHeader("location", "/URL_WITH_PARAMS");
// you still need 'return' because of react structure
return {
props: {
data
},
}
}
Notice: Router.push() only client-side (rendering)
P.S You can pass any params and retrieve it page you land. Check documentation

Running query from inside Cloud Function using request parameters

I am having troubles running queries from Cloud Functions using the request parameters to build the query form HTTP calls. In the past, I have ran queries from cloud functions fine with no error. My problem arises when I try to run the query using parameters gotten from the request.
When I hardcode the location of the document in the function, it works fine but when I try to build a query, it returns status code of 200. I have also logged the the built query and it is logging out the right thing but no data is being returned. It only returns data when the document path is hardcoded. See code below.
Query looks like this
https://us-central1-<project-id>.cloudfunctions.net/getData/CollectionName/DocumentName
export const getData = functions.https.onRequest((request, response) => {
const params = request.url.split("/");
console.log("the params 0 "+params[0]);
console.log("the params 1 "+params[1]);
console.log("the params 2 "+params[2]);
//Build up the document path
const theQuery = "\'"+params[1]+"\/"+params[2]+"\'";
console.log("the query "+theQuery); <-- logs out right result in the form 'Collection/Document'
//Fetch the document
const promise = admin.firestore().doc("\'"+params[1]+"\/"+params[2]+"\'").get() <---- This doesnt work, building the query
//const promise = admin.firestore().doc('collectionName/DocID').get() <---- This hard coded and it works
promise.then(snapshot => {
const data = snapshot.data()
response.send(data)
}).catch(error => {
console.log(error)
response.status(500).send(error);
})
});
I tried using a different approach and giving the datafields a names as seen below
Query looks like this
https://us-central1-<project-id>.cloudfunctions.net/getData?CollectionName=CName&DocumentID=Dname
export const getData = functions.https.onRequest((request, response) => {
const collectName = request.query.CollectionName;
const DocId = request.query.DocumentName;
//Build up the document path
const theQuery = "'"+collectName+"\/"+collectName+"'";
console.log("the query "+theQuery); <---Logs out correct result
//Fetch the document
const promise = admin.firestore().doc(theQuery).get() <-- Building the query does not work
//const promise = admin.firestore().doc('collectionName/DocID').get() <---- This hard coded and it works
promise.then(snapshot => {
const data = snapshot.data()
response.send(data)
}).catch(error => {
console.log(error)
response.status(500).send(error);
})
});
In both cases, when the request is build from the URL, it does not return any data and it does not return any errors. And I am sure the documents I am trying to fetch exsist in the database. Am I missing anything ?
Try request.path. Then you can obtain the path components, e.g. request.path.split("/")[1]
The syntax for request.query is valid when using Express. This is referenced in some of the docs, but not made explicit that Express is required. It's confusing.
To properly handle the dynamic inputs, you may have more luck working with Express and creating routes and handlers. This Firebase page has links to some projects using it.
Walkthough set-up using Express on Firebase.

Categories

Resources