Why don't my query params exist when using useRouter? nextjs - next.js

I have a nextjs route that is being hit. Here is the url
localhost:8080/eft-files/133722?enableRegen=true.
In my /pages folder, here is the code I am using to get the query params.
const EftFileViewWrapper = () => {
const {
query: { eftFileId, enableRegen }
} = useRouter();
return (
<EftFileView
eftFileId={eftFileId}
enableRegen={enableRegen}
/>
);
};
When the app routes via a next/link, initially the enableRegen is true, but this file gets hit a second time and enableRegen is then undefined. eftFileId is always populated.
If I do a hard refresh on the page, and don't route to it via a next/link, I always get the right values for all query params.
I have had to use URLSearchParams to make this work, like this:
const EftFileViewWrapper = () => {
const {
query: { eftFileId, enableRegen }
} = useRouter();
let params = isInBrowser && new URLSearchParams(location.search);
return (
<EftFileView
eftFileId={eftFileId}
enableRegen={enableRegen || params.get("enableRegen")}
/>
);
};
I would rather stay in the nextjs ecosystem, but am not sure what to do. Any ideas?

useRouter works only with paths and doesn't parse query params.
I suggest you use window.location in order to obtain the search params and then parse it with URLSearchParams. Of course, check that the window object is presented:
let enableGen = false
if (typeof window !== "undefined") {
const params = new URLSearchParams(window.location.search)
enableGen = params.get("enableRegen")
}

Related

Using fetch data as array in Vue3

I'm using a composable provided by Vuejs.org that looks like this
// fetch.js
import { ref, isRef, unref, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
function doFetch() {
// reset state before fetching..
data.value = null
error.value = null
// unref() unwraps potential refs
fetch(unref(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
}
if (isRef(url)) {
// setup reactive re-fetch if input URL is a ref
watchEffect(doFetch)
} else {
// otherwise, just fetch once
// and avoid the overhead of a watcher
doFetch()
}
return { data, error }
}
And within the script tag I'm using
let loopableValues = ref([])
const { data, error } = useFetch(
'https://jsonplaceholder.typicode.com/todos/',
)
loopableValues.value = data
return { loopableValues }
My issue is that the variable "data" is not a pure array that I can loop using something like this in the template:
<div v-for="value in loopableValues" :key="value.id"> {{ value.id}}</div>
Cause when I run this I get "Uncaught (in promise) TypeError: value is undefined". So I think I need to handle the promise somehow in order to loop out the data as above, but I'm stuck and don't know how to go forward. Do I need to use a function on the data coming back from the fetch composable or is it something else I'm misssing?

useFetch of Nuxt 3 sends same request during refresh instead of updating URL

I try to do a simple thing: At route change, refetch data.
Here is my link:
<li v-for="category in categories" :key="category.id" class="mb-1">
<NuxtLink :to="{ query: { cat: category.slug } }">
{{category.title}}
</NuxtLink>
</li>
And my request:
<script setup>
const route = useRoute()
const { data:categories } = await useFetch('http://127.0.0.1:8000/api/tasks/category/', {
key: route.fullPath,
initialCache: false
})
const { data:tasks, refresh } = await useFetch(`http://127.0.0.1:8000/api/tasks/?cat=${route.query.cat}`, {
key: `tasks:${route.query.cat}`,
initialCache: false
})
watch(() => route.query.cat, () => refresh())
</script>
So, at click, url is well changed:
http://localhost:3000/tasks?cat=category1
http://localhost:3000/tasks?cat=category3
http://localhost:3000/tasks?cat=category2
But requests are the same (from Django DRF backend):
GET http://127.0.0.1:8000/api/tasks/?cat=category1
GET http://127.0.0.1:8000/api/tasks/?cat=category1
GET http://127.0.0.1:8000/api/tasks/?cat=category1
It seems that it keeps the first category, even with initialCache: false
As mentioned in the comments, the URL should not be provided as string
const { data:tasks, refresh } = await useFetch(`http://127.0.0.1:8000/api/tasks/?cat=${route.query.cat}`, {
key: `tasks:${route.query.cat}`,
initialCache: false
})
but as a function returning a string:
const { data:tasks, refresh } = await useFetch(() => `http://127.0.0.1:8000/api/tasks/?cat=${route.query.cat}`, {
key: `tasks:${route.query.cat}`,
initialCache: false
})
useFetch is "freezing" the API URL, the changes you make to the string directly will not be reflected. If you want to add parameters to your API URL, as an alternative to using a function, you can use the query option of useFetch. This option is reactive, that is when the ref with your route is changing, the query will reflect this update.
For your case, this will work:
const cat = ref('your_value')
const { data:tasks, refresh } = await useFetch('http://127.0.0.1:8000/api/tasks', {
query: { cat }
})
This results in http://127.0.0.1:8000/api/tasks?cat=your_value
You don't need to mess with the key option, as the key will be auto generated, and you can also leave the cache alone.
The query option is not well documented yet, as discussed in this nuxt issue. I've created a pull request on nuxt/framework to have it reflected in the documentation. Please see a full explanation below:
Using the query option, you can add search parameters to your query. This option is extended from unjs/ohmyfetch and is using ufo to create the URL. Objects are automatically stringified.
const param1 = ref('value1')
const { data, pending, error, refresh } = await useFetch('https://api.nuxtjs.dev/mountains',{
query: { param1, param2: 'value2' }
})
This results in https://api.nuxtjs.dev/mountains?param1=value1&param2=value2

NextJS case insensitive route for SSG pages

I am using NextJS to translate a CSV of data into static pages. Each page is pages/[slug].jsx. I'm calling toLowerCase() on the slug value inside [slug].jsx getStaticPaths() and getStaticProps() functions. The generated pages are lowercase. e.g. /ab101, /ab102, /cc500 all resolve to the pages/[slug].jsx page.
Unfortunately, people might hand type the url and may use caps or mixed case for the slug value, currently resulting in a 404.
QUESTION: How can I make routing case insensitive with respect to the slug value?
UPDATE
When I return fallback: true from getStaticPaths(), my [slug].jsx file is hit even when there is not an exact path match. I can then check isFallback as illustrated by Anish Antony below.
Additionally, the items param that is passed to my page will be undefined when the page wasn't found. The router's pathname value is "/[slug]" and not the value of "slug". However, there is an asPath value which contains useful data, e.g. /mixedCaseSlug?param=value&foo=bar.
When the page renders, I check if it's a fallback. If it is, show a LOADING... message. Next will display that and call getStaticProps() to generate the "missing" page. You'll then re-render with the page data. In the event that getStaticProps couldn't get page data, I push a path that will lead to the built-in 404 page.
export default function Page({ item }) {
const { isFallback, push } = useRouter()
const hasPageData = item && Object.keys(item).length > 0
useEffect(() => {
if (!isFallback && !hasPageData) {
push('/page-not-found/error')
}
}, [isFallback, hasPageData])
const loadingMsg = <div>Loading...</div>
const notFoundMsg = <div>Page not found</div>
return isFallback ? loadingMsg : hasPageData ? <Item item={item} /> : notFoundMsg
}
I needed to update getStaticProps() to lowercase the slug param, as it may now be mixed case, but we want to find our page data. And I needed to allow for the case when there really is no data for the slug.
export async function getStaticProps({ params }) {
const { slug } = params
const item = data.find(o => o.Practice_Code.trim().toLowerCase() === slug.toLowerCase())
return {
props: {
item: item ? item : {}
}
}
}
This all seems very kludgy, so I'm still wondering if there is a better way.
NextJS routes are case sensitive.You can use fallback property in getStaticPaths to catch the routes which aren't in the same case as the one provided by default in getStaticPaths.
Edit: I have updated the answer based on the discussion with Dave.
We can give fallback:true or fallback:"blocking" , if we give fallback:true we we can show a custom component which will be displayed till the time page is loaded.For fallback:"blocking" new paths not returned by getStaticPaths will wait for the HTML to be generated,
When we give fallback:true or "blocking" static page will be generated when the user first access the site and the generated page will be served for further visits.
Sample code
export async function getStaticPaths() {
const idList = await fetchAllIds();
const paths = [
];
idList.forEach((id) => {paths.push(`/posts/${id}`)})
return { paths, fallback: true };
}
What we have note is our code in getStaticProps should be case insensitive to get the data irrespective of the one provided in url.
export async function getStaticProps({ params }) {
const { slug } = params;
try {
/// This fetch api should be able to fetch the data irrespective of the case of the slug
or we should convert it to the required case before passing it as a parameter to API
const data= await fetchSlugDetails(slug);
//Same logic should be performed if you are getting data filtered based on slug from an existing array.
return data? { props: { data} } : { notFound: true };
} catch (error) {
console.error(error);
return { notFound: true };
}
}
Note: You have to handle the notfound case and fallback case if you are using fallback:true. For fallback you can get the value isFallback from next/router while the page is being static generated

Get values from SvelteKit's $app/stores outside of the lifecycle of a component

My Svelte components import readable stores like this:
import { classes, locations, schedule } from 'stores.ts'
In stores.ts, I want to build the URL for fetch dynamically using page.host from $app/stores.
// Note: this is not a Svelte component; it's stores.ts
import { readable } from 'svelte/store'
import { getStores } from '$app/stores'
const { page } = getStores()
let FQDN
page.subscribe(({ host }) => {
FQDN = host
})
const getArray = async (url) => {
const response: Response = await fetch(url)
if (!response.ok) throw new Error(`Bad response trying to retrieve from ${url}.`)
return await response.json()
}
const getReadableStore = (url: string) => readable([], set => {
getArray(`http://${FQDN}${url}`)
.then(set)
.catch(err => console.error('Failed API call:', err))
return () => {}
})
export const classes = getReadableStore('/api/class/public.json')
export const locations = getReadableStore('/api/location/public.json')
export const schedule = getReadableStore('/api/schedule/public.json')
The sixth line throws this error...
Error: Function called outside component initialization
at get_current_component (/Users/nates/dev/shy-svelte/node_modules/svelte/internal/index.js:652:15)
at Proxy.getContext (/Users/nates/dev/shy-svelte/node_modules/svelte/internal/index.js:685:12)
at Module.getStores (/.svelte-kit/dev/runtime/app/stores.js:17:26)
at eval (/src/stores.ts:6:38)
at instantiateModule (/Users/nates/dev/shy-svelte/node_modules/#sveltejs/kit/node_modules/vite/dist/node/chunks/dep-e9a16784.js:68197:166)
Two questions...
What is the correct way to get page values from $app/stores outside of the context of a component? Is this possible? Answer from below: No, this is not possible outside the context of a component.
If I'm accessing a SvelteKit site, let's say http://localhost:3000/something or https://example.com and a Svelte component loads a readable store from stores.ts, is there a way in stores.ts to determine whether the original page request that loaded the component (which loaded from stores.ts) was http or https? Answer from below: No, this is not possible in stores.ts - only from a component.
UPDATE: Based on the feedback, I'm going to set a value in my .env called VITE_WEB_URL=http://localhost:3000 and change it for the production system. This cuts down on the number of lines of code and may be a better practice (comments welcome)...
// revised stores.ts
import { readable } from 'svelte/store'
const { VITE_WEB_URL } = import.meta.env
const getArray = async (url) => {
const response: Response = await fetch(url)
if (!response.ok) throw new Error(`Bad response trying to retrieve from ${url}.`)
return await response.json()
}
const getReadableStore = (url: string) => readable([], set => {
getArray(`${VITE_WEB_URL}${url}`)
.then(set)
.catch(err => console.error('Failed API call:', err))
return () => {}
})
export const classes = getReadableStore('/api/class/public.json')
export const locations = getReadableStore('/api/location/public.json')
export const schedule = getReadableStore('/api/schedule/public.json')
Extract from https://kit.svelte.dev/docs#modules-$app-stores
Because of that, the stores are not free-floating objects: they must be accessed during component initialisation, like anything else that would be accessed with getContext.
Therefore, since the readable store is bound to the context of a svelte component, I suggest you subscribe either way ($ or .subscribe) inside the component of the SvelteKit website and then send the protocol value (http or https) as parameter when it updates so that stores.ts stores it in a variable.
However, it looks like SvelteKit does not provide the protocol value, so parse the client side window.location.href in the page subscription and then send it.
Referencing a svelte store can be done everywhere.
Using the $: shorthand syntax, however, only works within a component.
$: BASE = `http://${$page.host}`
SvelteKit appears to delegate this to fetch indeed

correct way to fire out a fail result in page reponse event listener in codeceptJS

I wrote a helper methods to add a network response listener over Puppeteer page instance. the code looks like this
let Helper = codecept_helper;
class CheckHelper extends Helper {
async listenRequest(listener)
{
const helper = this.helpers['Puppeteer'];
await helper.page.setRequestInterception(true);
helper.page.on("request",listener);
return helper._waitForAction();
}
async listenResponse(listener)
{
const helper = this.helpers['Puppeteer'];
helper.page.on("response",listener);
return helper._waitForAction();
}
}
module.exports = CheckHelper;
then in the test script
let self=this;
I.listenResponse((response)=>{
if(response.url().match(/github.*\.js/) && response.headers()['content-length']>1000) {
//codeceptjs.event.emit(codeceptjs.event.test.failed, self, 'js file is too big!');
//codeceptjs.recorder.throw('js file is too big!')
//codeceptjs.recorder.stop();
//throw new Error('js file is too big!')
}
})
I.amOnPage("https://www.github.com");
i first add response listener, then i goto "github", when some js file size is too big,i will throw out an error,in order too check content size is correctly.
however, even i throw error out (like the comments codes did), the main test flow just not stop, how do i do is the right way?
well,i found a solution later
i recorded all the page response into a custom object in the page instance.
later i wrote a help methods to check whole records.
//in helper.js
startRecordResponse() {
const helper = this.helpers['Puppeteer'];
helper.page.on("response", (res) => {
//record all response instance into savedResponse object inside page, we'll use it later
helper.page.savedResponse = helper.page.savedResponse || {};
helper.page.savedResponse[res.url()] = res;
});
return helper._waitForAction();
}
checkFileIsTooBig(filter, sizeLimit) {
const helper = this.helpers['Puppeteer'];
//use the data recorded in savedResponse object
Object.keys(helper.page.savedResponse).forEach((url) => {
var res = helper.page.savedResponse[url];
if (((filter instanceof RegExp && filter.test(url)) || (typeof filter == "string" && url.indexOf(filter) != -1)) && res.headers()['content-length'] > sizeLimit) {
throw new Error(`file ${url} is too big,${res.headers()['content-length']} > ${sizeLimit}`)
}
})
return helper._waitForAction();
}
then in test file
Before((I) => {
I.startRecordResponse();
I.amOnPage("https://www.github.com");
});
Scenario('github_test', (I) => {
//check a js file contain github is less than 100 bytes
I.checkFileIsTooBig(/github.*\.js/,100);
}
);

Resources