Query params update but app doesn't (sometimes)
Answered
berkserbet posted this in #help-forum
I have an ecommerce app with some filters. When the filters are checked the url on my site updates. [my site](https://storefront.community/)
But sometimes when the url changes the products don't get filtered. So there basically isn't a state change. I can recreate it, so it's not random - but I don't understand what could be the issue. I'm new to nextjs - but haven't seen url update not update the app.
But sometimes when the url changes the products don't get filtered. So there basically isn't a state change. I can recreate it, so it's not random - but I don't understand what could be the issue. I'm new to nextjs - but haven't seen url update not update the app.
898 Replies
a link to your site isnt relevant. Please post code showing where you get the params, and where you consume them
Sure, I just didn't want to post a ton of code. Thank you
It's just one page.
Here is /app/page.tsx
Here is /app/ProductBrowser.tsx
Here is /app/page.tsx
import React, { Suspense } from 'react'
import ProductBrowser from './ProductBrowser';
interface Props {
searchParams: {
r: string,
search: string,
genders: string,
sizes: string,
conditions: string,
countries: string,
brands: string,
pages: number
}
}
export default function Home({searchParams: { r, search, genders, sizes, conditions, countries, brands, pages }}: Props) {
return (
<main>
<Suspense fallback={<div className="h-screen flex items-center justify-center pb-56"><span className="loading loading-dots loading-lg text-secondary text-center h-screen flex items-center justify-center"></span></div>}>
<ProductBrowser search={search} selected_subreddits={r} selected_genders={genders} selected_sizes={sizes} selected_conditions={conditions} selected_countries={countries} selected_brands={brands} pages={pages}/>
</Suspense>
</main>
);
}
Here is /app/ProductBrowser.tsx
import React from 'react'
import { promises as fs } from 'fs';
import path from 'path'
import ProductCards from './ProductCards'
interface Props {
search: string,
selected_subreddits: string,
selected_genders: string,
selected_sizes: string,
selected_conditions: string,
selected_countries: string,
selected_brands: string,
pages: number
}
const ProductBrowser = async ({ search, selected_subreddits, selected_genders, selected_sizes, selected_conditions, selected_countries, selected_brands, pages }: Props) => {
const filePath = path.join(process.cwd(), 'json/listings.json');
const file = await fs.readFile(filePath, 'utf8');
const products = JSON.parse(file)
return (
<ProductCards products={products} search={search} selected_subreddits={selected_subreddits} selected_genders={selected_genders} selected_sizes={selected_sizes} selected_conditions={selected_conditions} selected_countries={selected_countries} selected_brands={selected_brands} pages={pages}/>
)
}
export default ProductBrowser
so…
I was about to share the last file
first off
Do you already see an issue?
there are some silly coding habits im gonna point out
so you can improve
Please do! I'm new to this
the search params are the same as the props for the component
you are directly passing them
so define them in the component only
export the interface
and import it to page and use it
then you dont have to spread searchParams
you can do <ProductBrowser {...searchParams} />
I think I get what you're saying
secondly
Let me quickly try that
ALL search params are string
doesnt matter that it can be a number. it is a string
so if you want to make pages a number, you need to do that after
thirdly, all searchParams can also be an array of strings
so you need to check for that
So I updated my
app/page.tsx
to be this:import React, { Suspense } from 'react'
import ProductBrowser from './ProductBrowser';
interface Props {
searchParams: {
r: string,
search: string,
genders: string,
sizes: string,
conditions: string,
countries: string,
brands: string,
pages: number
}
}
export default function Home({searchParams}: Props) {
return (
<main>
<Suspense fallback={<div className="h-screen flex items-center justify-center pb-56"><span className="loading loading-dots loading-lg text-secondary text-center h-screen flex items-center justify-center"></span></div>}>
<ProductBrowser searchParams={searchParams}/>
</Suspense>
</main>
);
}
Just exporting the searchParams
well… you can do it that way too… but you dont need to
Can I access search params directly from components without passing it around?
export type ProductSearchParams: Record<'r' | 'search' | 'genders' | 'sizes' | 'conditions' | 'countries' | 'brands' | 'pages', string | string[]>
put that in your ProductBrowser.tsx
and change your Props to that
so remove Props interface completely
then also import that into your page.tsx
Sorry I'm a bit confused - what's the first thing I need to do from my existing codebase?
interface Props {
searchParams: ProductSearchParams
}
export default function Home({searchParams}: Props)
copy that type into your product browser file
Giving a few errors
Is it supposed to be =
rather than :
Also
'Record' only refers to a type, but is being used as a value here.ts(2693)
im on mobile
Errors gone
show me your product browser page again now updated
I haven't been able to het it to work yet, trying to understand the page.tsx changes
import React from 'react'
import { promises as fs } from 'fs';
import path from 'path'
import ProductCards from './ProductCards'
export type ProductSearchParams = Record<'r' | 'search' | 'genders' | 'sizes' | 'conditions' | 'countries' | 'brands' | 'pages', string | string[]>
interface Props {
search: string,
selected_subreddits: string,
selected_genders: string,
selected_sizes: string,
selected_conditions: string,
selected_countries: string,
selected_brands: string,
pages: number
}
const ProductBrowser = async ({ search, selected_subreddits, selected_genders, selected_sizes, selected_conditions, selected_countries, selected_brands, pages }: Props) => {
const filePath = path.join(process.cwd(), 'json/listings.json');
const file = await fs.readFile(filePath, 'utf8');
const products = JSON.parse(file)
return (
<ProductCards products={products} search={search} selected_subreddits={selected_subreddits} selected_genders={selected_genders} selected_sizes={selected_sizes} selected_conditions={selected_conditions} selected_countries={selected_countries} selected_brands={selected_brands} pages={pages}/>
)
}
export default ProductBrowser
I don't use ProductSearchParams
nice. delete Props
Cool, app still works
change the : Props in the component to ProductSearchParams
remove all the selected_ prefix nonsense
Then what do I pass to ProductCards?
the props
const ProductBrowser = async ({}: ProductSearchParams) => {
const filePath = path.join(process.cwd(), 'json/listings.json');
const file = await fs.readFile(filePath, 'utf8');
const products = JSON.parse(file)
return (
<ProductCards products={products} search={search} selected_subreddits={selected_subreddits} selected_genders={selected_genders} selected_sizes={selected_sizes} selected_conditions={selected_conditions} selected_countries={selected_countries} selected_brands={selected_brands} pages={pages}/>
)
}
export default ProductBrowser
just named properly
nono
leave the props
just remove the prefix
you added a selected_ prefix for no reason
to all the keys
import React from 'react'
import { promises as fs } from 'fs';
import path from 'path'
import ProductCards from './ProductCards'
export type ProductSearchParams = Record<'r' | 'search' | 'genders' | 'sizes' | 'conditions' | 'countries' | 'brands' | 'pages', string | string[]>
const ProductBrowser = async ({ r, search, genders, sizes, conditions, countries, brands, pages }: ProductSearchParams) => {
const filePath = path.join(process.cwd(), 'json/listings.json');
const file = await fs.readFile(filePath, 'utf8');
const products = JSON.parse(file)
return (
<ProductCards products={products} search={search} selected_subreddits={r} selected_genders={genders} selected_sizes={sizes} selected_conditions={conditions} selected_countries={countries} selected_brands={brands} pages={pages}/>
)
}
export default ProductBrowser
yes exactly
and when we are done you can do the same thing to ProductCards
but 1 thing at a time
Seeing a lot of
Type 'string | string[]' is not assignable to type 'string'.
Type 'string[]' is not assignable to type 'string'.
yep. what i was saying you need to check for. we will get to that
go to page.tsx
import ProductSearchParams
from your other file
Done
import { ProductSearchParams } from './ProductBrowser';
make your searchParams: ProductSearchParams
in your page props def
import React, { Suspense } from 'react'
import ProductBrowser from './ProductBrowser';
import { ProductSearchParams } from './ProductBrowser';
interface Props {
ProductSearchParams: {
r: string,
search: string,
genders: string,
sizes: string,
conditions: string,
countries: string,
brands: string,
pages: number
}
}
export default function Home({ProductSearchParams: { r, search, genders, sizes, conditions, countries, brands, pages }}: Props) {
return (
<main>
<Suspense fallback={<div className="h-screen flex items-center justify-center pb-56"><span className="loading loading-dots loading-lg text-secondary text-center h-screen flex items-center justify-center"></span></div>}>
<ProductBrowser search={search} selected_subreddits={r} selected_genders={genders} selected_sizes={sizes} selected_conditions={conditions} selected_countries={countries} selected_brands={brands} pages={pages}/>
</Suspense>
</main>
);
}
then where you have your ProductBrowser in page.tsx just make it <ProductBrowser {...searchParams } />
nono
I see. 🙂
import React, { Suspense } from 'react'
import ProductBrowser from './ProductBrowser';
import { ProductSearchParams } from './ProductBrowser';
interface Props {
searchParams: ProductSearchParams
}
export default function Home({searchParams: { r, search, genders, sizes, conditions, countries, brands, pages }}: Props) {
return (
<main>
<Suspense fallback={<div className="h-screen flex items-center justify-center pb-56"><span className="loading loading-dots loading-lg text-secondary text-center h-screen flex items-center justify-center"></span></div>}>
<ProductBrowser {...searchParams } />
</Suspense>
</main>
);
}
Error is
Cannot find name 'searchParams'. Did you mean 'URLSearchParams'?ts(2552)
import React, { Suspense } from 'react'
import ProductBrowser from './ProductBrowser';
import { ProductSearchParams } from './ProductBrowser';
interface Props {
searchParams: ProductSearchParams
}
export default function Home({ searchParams }: Props) {
return (
<main>
<Suspense fallback={<div className="h-screen flex items-center justify-center pb-56"><span className="loading loading-dots loading-lg text-secondary text-center h-screen flex items-center justify-center"></span></div>}>
<ProductBrowser {…searchParams} />
</Suspense>
</main>
);
}
simple
like this
gotta change those dots tho
iphone fucks up the dots
gotta type them manually haha
Magic
now before we move on
where are you going to host this thing
Vercel I host it on
ok. then your fs read is silly and wasteful
and the component doesnt need async nor suspense
🙂
just import the json
The json can get big
doesnt matter
And changes often
Cool
it doesnt change without redeploying
Correct
so its literally the same thing either way
Trying
import products from '../json/listings.json'
?you use cwd
so 1 dot
VScode autofilled
Got it
🙂
ok so remove async and just pass that along
also remove suspense from parent as the compnent isnt async so it doesnt do anything
New page.tsx:
import React, { Suspense } from 'react'
import ProductBrowser from './ProductBrowser';
import { ProductSearchParams } from './ProductBrowser';
interface Props {
searchParams: ProductSearchParams
}
export default function Home({ searchParams }: Props) {
return (
<main>
<ProductBrowser {...searchParams} />
</main>
);
}
so clean
Current ProductBrowser.tsx
import React from 'react'
import ProductCards from './ProductCards'
export type ProductSearchParams = Record<'r' | 'search' | 'genders' | 'sizes' | 'conditions' | 'countries' | 'brands' | 'pages', string | string[]>
import products from './json/listings.json'
const ProductBrowser = ({ r, search, genders, sizes, conditions, countries, brands, pages }: ProductSearchParams) => {
return (
<ProductCards products={products} search={search} selected_subreddits={r} selected_genders={genders} selected_sizes={sizes} selected_conditions={conditions} selected_countries={countries} selected_brands={brands} pages={pages}/>
)
}
export default ProductBrowser
less clean haha
I don't even need the productbrowser now I think
you do not 🙂
I can just pass directly
so update your Product card params to use the same logic you used for the browser
On it
So my ProductCards.tsx starts as:
'use client'
import React from 'react'
import Link from 'next/link'
import SearchContainer from './SearchContainer';
import Image from 'next/image'
import { useRouter } from 'next/navigation';
export type ProductSearchParams = Record<'r' | 'search' | 'genders' | 'sizes' | 'conditions' | 'countries' | 'brands' | 'pages', string | string[]>
import products from './json/listings.json'
do you wanna make the ProductSearchParams cleaner?
how comfortable are you with typescript
const productSearchParams = ['r', 'search', 'genders', 'sizes', 'conditions', 'countries', 'brands', 'pages'] as const;
type ProductSearchParam = typeof allSearchParams[number];
export type ProductSearchParams = Record<ProductSearchParam, string | string[]>;
tada
Amazing!
if any of that is confusing, ask
copying without understanding is the worst sin lol
What is "as const"
coding on phone is such a pain lol
What is it for I mean
means its read only and cant be edited
so its “static†basically
you cant infer a type from a mutable array
What is allSearchParams?
only a static (immutable) arrat
I don't have that variable
just a list of the keys we will use, separated for organization purposes
its the first thing
oh
whoops
all should be product
Got it
again. phone -,/
so the type is saying “this type is all the keys of this arrayâ€
So what does the second line do
type ProductSearchParam = typeof allSearchParams[number]
what i just said
Is it looping somehow
knew you would ask haha
its the typescript way to say “all the items in the array as type optionsâ€
when you hover over it
does it show you the options?
Like that?
yes but hover over the left side
the type itself
This one?
see what it did?
it plucked all the items out of the array and made them “this or this or this†as a type
But how does it know the type?
We haven't given any
Got it
“just typescript thingsâ€
😂
🙂
and you know what a Record is?
And I get the third line
ok cool
a record is just an object, but where you already know all the key and value types
so you dont have to manually define it because it can be inferred
your original one was a “manual†record
this one is “automatic†because we have a key type
Which is string or array of strings
Right?
thats the value type
the key type is the array of productSearchParams
so string…. but “specificâ€
Very cool
so record says “for each item in the key type, the value type isâ€
The next thing I did was import the json
do you have a type for your json?
import listings from './json/listings.json'
But giving error
what error
screenshot your folder/file window
'listings' is declared but its value is never read.ts(6133)
Cannot find module './json/listings.json' or its corresponding type declarations.ts(2307)
Cannot find seems to be important
I think it's in the right spot
is app and json at the same level?
do you have type aliases in tsconfig?
it would help you
not type
path aliases
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
},
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "*/listings.json"],
"exclude": ["node_modules"]
}
This is what I got
so you can import smarter
"baseUrl": "./src",
"paths": {
"@modules/*": ["rest/modules/*"],
"@services/*": ["services/*"]
}
Found this online
Like that?
import Listings from “@/json/listings.json"
you can import this way
with the aliases
much prettier imo anyway haha
Yup!
No error
whats your listings json look like
just show me like the first 20-30 lines
[
{
"title": "Lululemon City Keeper Gloves",
"brands": [
"Lululemon"
],
"actions": [
"Selling"
],
"status": "Active",
"genders": [
"Women"
],
"sizes": [],
"categories": [],
"price": 29,
"currency": "USD",
"countries": [],
"images": [
"https://i.redd.it/71paagbg2afc1.jpg",
"https://i.redd.it/7rfw7gbg2afc1.jpg"
],
"conditions": [
"New With Tags"
],
"colors": [],
"shipping_info": "Shipping included.",
"search_keywords": null,
"timestamp": 1706489846,
"source": "Reddit",
"reddit_subreddit": "lululemonBST",
"reddit_author": "mrasifs",
"reddit_post_id": "1adie9g",
"reddit_thread_id": null,
"reddit_comment_id": null,
"listing_number": 0
},
{
"title": "Og Align 25\u201d (4) Dark Forest",
"brands": [
"Lululemon"
],
"acti
interface Product {
title: string;
brands: string[];
actions: string[];
status: string | null;
genders: string[];
sizes: string[];
categories: string[];
price: number | null;
currency: string | null;
countries: string[];
images: string[];
conditions: string[];
colors: string[];
shipping_info: string | null;
search_keywords: string | null;
timestamp: number; // in seconds
source: string;
reddit_subreddit: string;
reddit_author: string;
reddit_post_id: string | null;
reddit_thread_id: string | null;
reddit_comment_id: string | null;
listing_number: number;
}
Defined here
wheres that at
In ProductCards.tsx
make yourself a types folder
so you keep things organized
But that is the only type
No other one
is it? we have already made another type
😉
allSearchParams?
Yeah
and you will make more.
Where should I put the types folder?
same level as json
outside app
Created it
make a products.ts file
move that type and the 3 lines we made into that file
to keep things organized
then fix imports if needed
const allSearchParams = ['r', 'search', 'genders', 'sizes', 'conditions', 'countries', 'brands', 'pages'] as const;
type ProductSearchParam = typeof allSearchParams[number];
export type ProductSearchParams = Record<ProductSearchParam, string | string[]>;
interface Product {
title: string;
brands: string[];
actions: string[];
status: string | null;
genders: string[];
sizes: string[];
categories: string[];
price: number | null;
currency: string | null;
countries: string[];
images: string[];
conditions: string[];
colors: string[];
shipping_info: string | null;
search_keywords: string | null;
timestamp: number; // in seconds
source: string;
reddit_subreddit: string;
reddit_author: string;
reddit_post_id: string | null;
reddit_thread_id: string | null;
reddit_comment_id: string | null;
listing_number: number;
}
Thats the whole file
dont forget to export Product
const allSearchParams = ['r', 'search', 'genders', 'sizes', 'conditions', 'countries', 'brands', 'pages'] as const;
type ProductSearchParam = typeof allSearchParams[number];
export type ProductSearchParams = Record<ProductSearchParam, string | string[]>;
export interface Product {
title: string;
brands: string[];
actions: string[];
status: string | null;
genders: string[];
sizes: string[];
categories: string[];
price: number | null;
currency: string | null;
countries: string[];
images: string[];
conditions: string[];
colors: string[];
shipping_info: string | null;
search_keywords: string | null;
timestamp: number; // in seconds
source: string;
reddit_subreddit: string;
reddit_author: string;
reddit_post_id: string | null;
reddit_thread_id: string | null;
reddit_comment_id: string | null;
listing_number: number;
}
ok. very nice
Now trying import
My page.tsx:
import React from 'react'
import ProductCards from './ProductCards';
import ProductSearchParams from '@/types/products';
interface Props {
searchParams: ProductSearchParams
}
export default function Home({ searchParams }: Props) {
return (
<main>
<ProductCards {...searchParams} />
</main>
);
}
ProductSearchParams has an error
Module '"/Users/berkserbetcioglu/Code/storefront/types/products"' has no default export. Did you mean to use 'import { ProductSearchParams } from "/Users/berkserbetcioglu/Code/storefront/types/products"' instead?ts(2613)
Fixed
What even is the { } for?
good. if you couldnt figure that one out i might have given up on you
🙂
means an export in the file
since you can export many things
Also done with importing to ProductCard
'use client'
import React from 'react'
import Link from 'next/link'
import SearchContainer from './SearchContainer';
import Image from 'next/image'
import { useRouter } from 'next/navigation';
import products from '@/json/listings.json'
import { Product } from '@/types/products';
Still have this error
does it need to be client?
It has a bunch of product listings that update based on filters
So I think so
arent your filters in the url…?
Yeah
Ah
not client
😉
App broke, but I think I'm on the right track 🙂
I use
useRouter()
for what
to update the url?
Yeah
router.push(path, { scroll: false });
instead use redirect()
same thing and no use
Cool
import redirect from 'nextjs-redirect'
this one?nextjs/navigation iiirc
this
helpers gotta know the docs
or we would be lost
Updated
whats your productcards look like now
Unhandled Runtime Error
Error: Event handlers cannot be passed to Client Component props.
<input name="r" type=... id="r" value=... className=... defaultChecked=... onClick={function}>
^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
yeah you will change those to a form
with server action
Cool
pause for a sec
lets clean up the search params
This is the start of my Product Cards
import React from 'react'
import Link from 'next/link'
import SearchContainer from './SearchContainer';
import Image from 'next/image'
import products from '@/json/listings.json'
import { Product } from '@/types/products';
import { redirect } from 'next/navigation'
interface Props {
products: Product[],
search: string,
r: string,
genders: string,
sizes: string,
conditions: string,
countries: string,
brands: string,
pages: number
}
const ProductCards = ({r, search, genders, sizes, conditions, countries, brands, pages}: Props) => {
I know Props can be gone
in this case tho, we wanna sanitize your props
const ProductCards = ({r, search, genders, sizes, conditions, countries, brands, pages}: ProductSearchParams) => {
I did this
so lets make a helper function above ProductCards
function sanitize(params: ProductSearchParams) {
return params
}
?With like if's in there
we can be fancy though
sec
I like that
function sanitize(params: ProductSearchParams) {
const stringsOnly = Object.fromEntries(Object.entries(params).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v]));
return {... stringsOnly, pages: Number(stringsOnly.pages)};
}
does that func make sense?
For some of them I just use comma seperated strings
Then split by the comma to form an array
Is that ok?
comma separated is not how nextjs does arrays
so thats fine
to make an array someone would have to do ?pages=2&pages=3
multiple keys turn it into an array
I can have longish ones
thats totally fine
I don't quite understand
so lets work from inside to outside
you know what Object.entries does?
I don't
takes an object, and turns it into an array
each item in the array is an array of [key, value]
Got it
so it would be like
[["pages", "2"], ["r", "poo"]]
[["pages", "2"], ["r", "poo"]]
Perfect
we then map those values
Yup
and keep the key but check the value
If array then v[0]
Why is that?
yeah. basically say “if you manually put more than 1 key on my site i dont care i want the first oneâ€
Cool
never trust the user
stringsOnly has an array of arrays now
or a fart after age 30
jkjk
With the first val
Of each key
and each value as a string
Yup, then the last one is noing number conversion - not sure how
then because you want pages to be a number, we return a new object that we spread all the keys back in, but override the pages key to a number version
Oh cool
So little code to accomplish that
you could also just do
stringsOnly.pages = Number(stringsOnly.pages)
stringsOnly.pages = Number(stringsOnly.pages)
but i like clean short code
Yeah for sure
Where do I call sanitize?
so heres the fun part
your object spread you have in ProductCards definition
move that INSIDE the component
🙂 crazy
as like
const {put it here} = sanitize(props)
const {put it here} = sanitize(props)
Got it
and your func should just be (props: ProductSearchParams
then you get the same functionality, but clean
Some errors on the vars:
'const' declarations must be initialized.ts(1155)
Variable 'brands' implicitly has an 'any' type
const ProductCards = (props: ProductSearchParams) => {
const r, search, genders, sizes, conditions, countries, brands, pages = sanitize(props)
inside {}
Oh
its still an object
Like this?
const ProductCards = (props: ProductSearchParams) => {
const { r, search, genders, sizes, conditions, countries, brands, pages } = sanitize(props)
no errors right?
I have errors
Property 'r' does not exist on type '{ pages: number; }'.ts(2339)
i have emotional errors
🙂
hahahaha
I appreciate you so greatly
show me your sanitize func
function sanitize(params: ProductSearchParams) {
const stringsOnly = Object.fromEntries(Object.entries(params).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v]));
return {... stringsOnly, pages: Number(stringsOnly.pages)};
}
ok. we will do this the verbose way
go to your types
and export ProductSearchParam
so we have access to it
I do that
export type ProductSearchParams = Record<ProductSearchParam, string | string[]>;
Ah
export type ProductSearchParam = typeof allSearchParams[number];
ok so now at the end of your sanitize where you return the object
I imported it
Current:
return {... stringsOnly, pages: Number(stringsOnly.pages)};
add this at the end
as Record<Exclude<ProductSearchParam, 'pages'>, string> & { pages: number };
as Record<Exclude<ProductSearchParam, 'pages'>, string> & { pages: number };
Addind
im ready for the questions
😂
I get it
oh? very good
Exclude pages mark string
Pages number
my man
ok. is the other error now gone?
Why couldn't we use ProductSearchParams
Error gone
Because then we need to use a much fancier typescript type to exclude because that is already a record type
and i cant be assed to type that out on my phone
😅
Yup!
In my borwser localhost I see a related error
./app/ProductCards.tsx
Error:
× cannot reassign to a variable declared with `const`
â•â”€[/Users/berkserbetcioglu/Code/storefront/app/ProductCards.tsx:12:1]
12 │ }
13 │
14 │ const ProductCards = (props: ProductSearchParams) => {
15 │ const { r, search, genders, sizes, conditions, countries, brands, pages } = sanitize(props)
·
So in the declaration of those variables with sanitize
are you using those variable names anywhere else?
inside that component
I likely am
if you want to be safe
I will update
you can not destructure it
and save as like. initialParams
then get them via initialParams.whatever
Ah
Yeah sounds good
const { initialParams } = sanitize(props)
Like that?
const initialParams = sanitize(props)
Like that
yeah exactly
Cool, worked
Updating vars in file
once you change your router.push to redirects, your problem should be gone
Getting an error with my products variable
I imported like this:
import products from '@/json/listings.json'
The error is long:
Argument of type '({ title: string; brands: string[]; actions: string[]; status: string; genders: string[]; sizes: string[]; categories: string[]; price: number; currency: string; countries: string[]; images: string[]; conditions: string[]; ... 10 more ...; listing_number: number; } | ... 29 more ... | { ...; })[]' is not assignable to parameter of type 'Product[]'.
Type '{ title: string; brands: string[]; actions: string[]; status: string; genders: string[]; sizes: string[]; categories: string[]; price: number; currency: string; countries: string[]; images: string[]; conditions: string[]; ... 10 more ...; listing_number: number; } | ... 29 more ... | { ...; }' is not assignable to type 'Product'.
Type '{ title: string; brands: string[]; actions: string[]; status: string; genders: string[]; sizes: string[]; categories: string[]; price: number; currency: string; countries: string[]; images: string[]; conditions: string[]; ... 10 more ...; reddit_thread_id?: undefined; }' is not assignable to type 'Product'.
Types of property 'reddit_thread_id' are incompatible.
Type 'undefined' is not assignable to type 'string | null'.ts(2345)
I either put an empty array if array type or null
Types of property 'reddit_thread_id' are incompatible.
Type 'undefined' is not assignable to type 'string | null'.ts(2345)
Hmm
according to this, you have an undefined somewhere in that json, but dont have it in your type
Let me check
show me your Project interface
--What is that?--
export interface Product {
title: string;
brands: string[];
actions: string[];
status: string | null;
genders: string[];
sizes: string[];
categories: string[];
price: number | null;
currency: string | null;
countries: string[];
images: string[];
conditions: string[];
colors: string[];
shipping_info: string | null;
search_keywords: string | null;
timestamp: number; // in seconds
source: string;
reddit_subreddit: string;
reddit_author: string;
reddit_post_id: string | null;
reddit_thread_id: string | null;
reddit_comment_id: string | null;
listing_number: number;
}
?reddit_thread_id: string | null;
Like that?
reddit_thread_id?: string | null;
Done
error gone?
Nope seemingly there will be more with that json
Argument of type '({ title: string; brands: string[]; actions: string[]; status: string; genders: string[]; sizes: string[]; categories: string[]; price: number; currency: string; countries: string[]; images: string[]; conditions: string[]; ... 10 more ...; listing_number: number; } | ... 29 more ... | { ...; })[]' is not assignable to parameter of type 'Product[]'.
Type '{ title: string; brands: string[]; actions: string[]; status: string; genders: string[]; sizes: string[]; categories: string[]; price: number; currency: string; countries: string[]; images: string[]; conditions: string[]; ... 10 more ...; listing_number: number; } | ... 29 more ... | { ...; }' is not assignable to type 'Product'.
Type '{ title: string; brands: string[]; actions: string[]; status: string; genders: string[]; sizes: string[]; categories: string[]; price: number; currency: string; countries: string[]; images: string[]; conditions: string[]; ... 10 more ...; reddit_thread_id?: undefined; }' is not assignable to type 'Product'.
Types of property 'shipping_info' are incompatible.
Type 'never[]' is not assignable to type 'string'.
I'm not sure what
never[]
isBut shipping_info is null often
basically, you need to match up your json with your type
so go through those 1 by 1 and fix
or fix the type
But why is it broken now?
for that one its adding [] to the shipping_info
It's the same json
yeah your json is wonky haha
doesnt adhere like you thought it did
so you are set?
Not quite, just settled errors in VScode
Now the form thing I believe
Error: Event handlers cannot be passed to Client Component props.
<input name="r" type=... id="r" value=... className=... defaultChecked=... onClick={function}>
^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
On it
Do functions that do redirects need to be client side?
redirect() is designed to work in server actions
Cool, I'm just trying to figure out how to turn my 6 <input>'s into <form>'s
I think
turn all into 1 form
Hmm
then you can build the url the same way regardless
This is what I have for each:
<input
name="r"
type="checkbox"
id="r"
value={subreddit_name}
className="checkbox checkbox-sm"
defaultChecked={checkHandler("r", {subreddit_name})}
onClick={(e) => handleClick(e.target)}
/>
find the closest parent that contains all the inputs
and add a <form> to wrap it
But I have 6 seperate dropdowns
Each dropdown has on input for many options
i understand 🤣 Its fine
trust.
Yeah
function handleClick(checkbox: any) {
if (typeof window !== "undefined") {
queryParams = new URLSearchParams(window.location.search);
}
const checkboxes = document.getElementsByName(checkbox.name);
let selected: string[] = [];
checkboxes.forEach((item: any) => {
if (item.checked) selected.push(item.value);
});
if (selected.length === 0) {
queryParams.delete(checkbox.name);
} else {
queryParams.set(checkbox.name, selected.join(','));
}
queryParams.delete("pages");
const path = window.location.pathname + "?" + queryParams.toString();
redirect(path);
}
Probably badly
sokay. we can clean it up.
leave the function, but remove the onClick for all of them
Sure
Done
one question... does the user need to submit at all?
or are you immediately changing the value
Immediate change
I also have a "Load More..." button that increments page
All others are checkboxes
ok. we are gonna make input (JUST INPUT) a client component
make a new file
like... checkbox.tsx
On it
I have the file
Actuallly
I have a seperate searchbar
It's in a seperate component
With 'use client'
So not only immidiate
But in ProductCards all immidiate
yeah thats fine
Awesome
What do I put in the file?
'use client';
export const CheckBox = (props: DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>) => {
const onClick = (e: any) => e.currentTarget.form?.requestSubmit();
return <input {...props} onClick={onClick} />
}
was typing it out
Amazing thanks!
Can't find
InputHTMLAttributes
and DetailedHTMLProps
so trying to figure out import
import { DetailedHTMLProps, InputHTMLAttributes } from "react";
Awesome worked
should be a drop-in replacement for your <input>
Testing!
well you still need to make your server action
So I replace the input with CheckBox?
Yup
The onclick is what I need to do now
function submitForm(formData: FormData) {
'use server';
console.log("Form Data:", formData);
}
start with this
<form action={submitForm}>
Where should I have form?
I see
you are just adding the action prop
Yeah
Trying to figure this out:
To use Server Actions, please enable the feature flag in your Next.js config. Read more: https://nextjs.org/docs/app/building-your-application/data-fetching/forms-and-mutations#convention
are you using an old version of next...?
yeah old
14.1.0
I'll update
It's because my node was older
I have an issue when trying to update
Working on it
I seemingly have multiple versions
But next is pointing at an old one
let me know when you sort that XD
I think it worked now
Sorry about that
multitasking
Seeing:
× Server actions must be async functions
Should I make it
yeah thats true
yes
Is this wrong?
async function submitForm(formData: FormData) {
'use server';
console.log("Form Data:", formData);
}
Big red error on browser
Same error
yeah thats fine
Ok yeah
Got a runtime error:
Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
did you leave onClick somewhere
gotta find where its happening
⨯ Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
<... loader={function} src=... onError=... className=... width=... height=... alt=...>
^^^^^^^^^^
at stringify (<anonymous>)
⨯ Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
<... loader={function} src=... onError=... className=... width=... height=... alt=...>
^^^^^^^^^^
at stringify (<anonymous>)
What I do I need to look for?
well...
i cant see your file
try commenting out whats in the CheckBox component first
to make sure its not that
Doesn't seem to be that
Can I send the file?
Matches what?
matches the error
the error is because you are using an image loader
if you want to keep doing that, you need to wrap your image component in a client component file
Yeah I have to I think
Lots of images in my app
On it
I assume I need to move the loader out too
I'm not quite sure how to wrap it
I created
image.tsx
So far have this in it:
'use client'
const imageKitLoader = ({
src,
width,
quality,
}: {
src: string;
width: number;
quality?: number;
}) => {
if (src[0] === '/') src = src.slice(1);
const params = [`w-${width}`];
if (quality) {
params.push(`q-${quality}`);
}
const paramsString = params.join(',');
var urlEndpoint = 'https://ik.imagekit.io/y7y0qqdyl';
if (urlEndpoint[urlEndpoint.length - 1] === '/')
urlEndpoint = urlEndpoint.substring(0, urlEndpoint.length - 1);
if (src.includes("redd.it")) {
return `${urlEndpoint}/r/${src.split("/").pop()}?tr=${paramsString}`;
} else if (src.includes("postimg")) {
return `${urlEndpoint}/postimg/${src.split(".cc/").pop()}?tr=${paramsString}`;
} else if (src.includes("imgur")) {
return `${urlEndpoint}/imgur/${src.split("/").pop()}?tr=${paramsString}`;
} else if (src.includes("https://ik.imagekit.io/y7y0qqdyl/")) {
return `${src}?tr=${paramsString}`;
} else {
return `https://ik.imagekit.io/y7y0qqdyl/Image_not_available_wide.webp?tr=${paramsString}`
}
};
export const CustomImage = (
export const CustomImage = (props: ComponentProps<typeof Image>) => {
return <Image {...props} loader={imageKitLoader} />
}
Great
Now I am getting a different issue with the
onError={onImageError}
I think I know how to fix
It doesn't like the
onError
: <CustomImage
src={image}
onError={onImageError}
className="w-full rounded-box object-contain"
width={480}
height={480}
alt="Item Picture"
/>
I move the function to the image.tsx
But it didn't work
Fixed!
Now app works
Weird error on left:
Error: Hydration failed because the initial UI does not match what was rendered on the server.
ignore for now
something to address later
Cool
So now I gotta update query params
but first click a button
and check the logs
so you know what the formData looks like
Form Data: FormData { [Symbol(state)]: [ { name: 'brands', value: 'Adidas' } ] }
Form Data: FormData {
[Symbol(state)]: [
{ name: 'conditions', value: 'New With Tags' },
{ name: 'brands', value: 'Adidas' }
]
}
Form Data: FormData {
[Symbol(state)]: [
{ name: 'conditions', value: 'New With Tags' },
{ name: 'conditions', value: 'Used' },
{ name: 'brands', value: 'Adidas' }
]
}
So it keeps updating
With new objects of clicks
I get it, works great
ok. ill leave you to sort the last bit, as you should have it under control. if you get super stuck though you can ping
For sure, thank you! it's just redirect right?
Awesome, will try now. Thank you so much for all the help. Is there anything I can do? I would love to pay for your lunch tomorrow
Nope no need
If you do share, where are you based?
Austin TX
Oh cool
I'm SF
What is
Symbol(state)
?I am trying to understand how to read it:
{ [Symbol(state)]: [ { name: 'sizes', value: '0' } ] }
Something about [FormData Iterator], not sure I undestand that
yeah its not a simple object
it has methods
you can do .get()
and .getAll() or something like that
Do I need to move my checkHandler?
But then the initialparams wont be accessible
function checkHandler(checkBoxType: string, checkBoxValue: any) {
if (typeof window !== "undefined") {
if (checkBoxType == 'r' && typeof initialParams.r !== "undefined") {
if (initialParams.r.split(",").includes(checkBoxValue['subreddit_name'])) { return true }
return false
}
if (checkBoxType == 'genders' && typeof initialParams.genders !== "undefined") {
if (initialParams.genders.split(",").includes(checkBoxValue['gender'])) { return true }
return false
}
if (checkBoxType == 'sizes' && typeof initialParams.sizes !== "undefined") {
if (initialParams.sizes.split(",").includes(checkBoxValue['size'])) { return true }
return false
}
if (checkBoxType == 'conditions' && typeof initialParams.conditions !== "undefined") {
if (initialParams.conditions.split(",").includes(checkBoxValue['condition'])) { return true }
return false
}
if (checkBoxType == 'countries' && typeof initialParams.countries !== "undefined") {
if (initialParams.countries.split(",").includes(checkBoxValue['country'])) { return true }
return false
}
if (checkBoxType == 'brands' && typeof initialParams.brands !== "undefined") {
if (initialParams.brands.split(",").includes(checkBoxValue['brand'])) { return true }
return false
}
return false
}
return false;
}
you dont need that anymore
all that data is in the object
But they don't stay selected
you need to set the value
from your params
Oh so instead of checkHandler
But why wouldnt checkHandler work
because its client side only
which is also why you have hydration errors
Should I mark it as client
doesnt need to be client
theres no reason to check window at all in that function
So how do I mark which checkboxes are checked?
fixing your func. sec.
ðŸ™
function checkHandler(param: ProductSearchParam, values: any) {
if (!initialParams[param] || param === 'pages') {
return false;
}
const options = initialParams[param].split(',')
switch (param) {
case "r":
return options.includes(values['subreddit_name']);
case "genders":
return options.includes(values['gender']);
case "sizes":
return options.includes(values['size']);
case "conditions":
return options.includes(values['condition']);
case "countries":
return options.includes(values['country']);
case "brands":
return options.includes(values['brand']);
default:
return false;
}
}
Amazing
i have no idea what
values
isbut... i assume you do
yeah
It's like sizes: M
value is M
Clothing
Couple errors
Property 'split' does not exist on type 'string | number'.
Property 'split' does not exist on type 'number'
Pages doesn't go to this one ever
Also
Cannot find name 'checkBoxType'. Did you mean 'CheckBox'?ts(2552)
checkBoxType is param now I think
oh. do a type check safey on the first if
fixed above
Amazing, now where is checkHandler called, same place?
well, before you had it on default checked
that would be an uncontrolled component
you now want controlled component
so change it to checked instead of defaultChecked
Seems to be working for that
When I manually enter a param its checked
Now just the submitForm 😄
Yeah how do I parse Symbol(state)
you dont need to
I just need to iterate and append text
How do I get the object to iterate throuch
go back to your types/product.ts
export your array
see all this stuff that ended up being super useful later
😄
const allSearchParams = ['r', 'search', 'genders', 'sizes', 'conditions', 'countries', 'brands', 'pages'] as const;
export type ProductSearchParam = typeof allSearchParams[number];
export type ProductSearchParams = Record<ProductSearchParam, string | string[]>;
export interface Product {
title: string;
brands: string[];
actions: string[];
status: string | null;
genders: string[];
sizes: string[];
categories: string[];
price: number | null;
currency: string | null;
countries: string[];
images: string[];
conditions: string[];
colors: string[];
shipping_info: string | null;
search_keywords: string | null;
timestamp: number; // in seconds
source: string;
reddit_subreddit: string;
reddit_author: string;
reddit_post_id: string | null;
reddit_thread_id?: string | null;
reddit_comment_id: string | null;
listing_number: number;
}
allSearchParams
export it
Do I export products = Product[]
Product array
nah thats just lazy lol
Then how? 🙂
export the array
Ah
Alright
And imported
Isn't there like a one line version to loop and create the query text
I would loop, over list and keep manually appending with
queryParams.set(key, value.join(','));
Loop over the formData
Form Data: FormData {
[Symbol(state)]: [
{ name: 'sizes', value: '4' },
{ name: 'sizes', value: '5' },
{ name: 'sizes', value: '6â€' }
]
}
I am trying to grab this part:
[
{ name: 'sizes', value: '4' },
{ name: 'sizes', value: '5' },
{ name: 'sizes', value: '6â€' }
]
then in your server action you can do something along the lines of
const submitForm = async (formData: FormData) => {
const newSearchParams = new URLSearchParams();
allSearchParams.forEach(p => {
const opts = formData.getAll(p)
if (opts.length) {
newSearchParams.append(p, opts.join(","));
}
})
redirect(`/current/url/path?${newSearchParams.toString()}`)
}
Nice!
Added 'use client'
you mean 'use server'
Yeah sorry
ok phew
const submitForm = async (formData: FormData) => {
'use server';
const newSearchParams = new URLSearchParams();
allSearchParams.forEach(p => {
const opts = formData.getAll(p)
if (opts.length) {
newSearchParams.append(p, opts.join(","));
}
})
redirect(`/current/url/path?${newSearchParams.toString()}`)
}
But still getting
Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
<form action={function} children=...>
How do you color like this?
add ts after the 3 backticks
```ts
oh cool
that doesnt really make any sense
you can move it to a server actions file
then move the 'use server' to the top of the file
and import it
see if it still does it
Sure
This doesn't throw an error:
async function submitForm(formData: FormData) {
'use server';
const newSearchParams = new URLSearchParams();
allSearchParams.forEach(p => {
const opts = formData.getAll(p)
if (opts.length) {
newSearchParams.append(p, opts.join(","));
}
})
redirect(`/current/url/path?${newSearchParams.toString()}`)
}
they hate my es6
But now no matter what I click my params are
Initial Params: { r: 'url', pages: NaN }
r=url
did you change the redirect to your actual path
🙂
Does redirect allow you to not scroll
i... i actually have no idea
The app is acting weird
Like the seach bar moves around after every button click
Then goes to it's normal place
that would be your client components not having proper css
which is a whole different issue
😄
But it gets to the right place
prerender vs hydration
prerender is only server known stuff
hydration is client side and final state
Are the hydration errors easy to fix?
once you understand what causes hydration errors in the first place, yes absolutely.
So how can I get my app to work as well as it used to now?
you have now acended
🙂
Seems I need to learn a few more things
this will - for better or worse - make you understand the importance of having the correct data on the server so that it matches and is not modified by the client
search around your code and check for anything that modifies how things look via a useEffect
thats your first "oh shit i see"
useEffect adds animation?
useEffect adds client javascript logic
of any kind
But why do I want that?
Like my searchbar only has:
flex items-center justify-center
Why does it move around so much
couple things can cause it.
1. you have client css-in-js somewhere.
2. you have values not being populated until client render
3. you have css that does not have stringent bounds causing it to grow or shrink until the item that changes where it flows to appears.
1. you have client css-in-js somewhere.
2. you have values not being populated until client render
3. you have css that does not have stringent bounds causing it to grow or shrink until the item that changes where it flows to appears.
css-in-js is a function that returns an item with css?
not necessarily. Its css that is defined dynamically via javascript
vs static classes / properties
My search looks like this:
'use client'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { useState } from 'react'
const SearchContainer = () => {
// NextJs Navigation
const router = useRouter()
const searchParams = useSearchParams()
const pathname = usePathname()
let queryParams: URLSearchParams;
// React States
const [search, setSearch] = useState(searchParams.get('search') || '')
// Handle Submitting
const handleSubmit = (e: any) => {
e.preventDefault()
if (typeof window !== "undefined") {
queryParams = new URLSearchParams(window.location.search);
}
queryParams.delete("pages");
if (search === '') {
queryParams.delete('search');
} else {
queryParams.set('search', search);
}
const path = window.location.pathname + "?" + queryParams.toString();
router.push(path);
}
const removeSearch = (e: any) => {
router.push(pathname)
return
}
return (
<form className='flex items-center justify-center'>
<input className='border border-gray-400 rounded-md p-1' placeholder='Search' onChange={(e) => setSearch(e.target.value)} defaultValue={search}/>
<button className='border border-gray-400 rounded-md p-1 m-2 hover:bg-blue-100' type='submit' onClick={handleSubmit}>ðŸ”</button>
<button type='submit' onClick={removeSearch}>✖ï¸</button>
</form>
)
}
export default SearchContainer
Does it just take time to run this one the client side each time
record what its doing. its anyones guess without being able to visualize the CLS
CLS is Cumulative Layout Shift?
Exactly 🙂
For the button that updates the pages number, do I need to create a new button component like the checkbox?
that one you just remove onClick
and instead use type="submit" action={}
(give it its own action so it can do the unique thing)
I use to use:
function handlePageClick() {
if (typeof window !== "undefined") {
queryParams = new URLSearchParams(window.location.search);
}
initialParams.pages = parseInt(queryParams.get("pages") || "1");
let nextPage: number = initialParams.pages + 1;
queryParams.set("pages", nextPage.toString());
const path = window.location.pathname + "?" + queryParams.toString();
redirect(path);
}
Is action a function?
action is a server action function
like the form one we made
To test this I created:
The button I made:
But now my button doesn't appear. And action shows this error:
async function updatePage() {
'use server';
console.log("Update Page.")
}
The button I made:
<button className="btn btn-accent btn-outline btn-lg" type="submit" action={updatePage()}>Load More...</button>
But now my button doesn't appear. And action shows this error:
Type '{ children: string; className: string; type: "submit"; action: Promise<void>; }' is not assignable to type 'DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>'.
Property 'action' does not exist on type 'DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>'
you dont add the () for actions
you are passing the definition of the function, not the function execution
So like this:
action={updatePage}
It gives the same error
oh i forgot. when on a button its formAction not action
Cool!
When I manually add a pages query param it just gets deleted
I tried this:
async function updatePage(formData: FormData) {
'use server';
console.log("Update Page.")
console.log("Form Data:", formData)
const newSearchParams = new URLSearchParams();
allSearchParams.forEach(p => {
const opts = formData.getAll(p)
if (opts.length) {
console.log("Form Data:", p, opts.join(","));
newSearchParams.append(p, opts.join(","));
}
})
newSearchParams.set("pages", (parseInt(newSearchParams.get("pages") || "1") + 1).toString())
redirect(`?${newSearchParams.toString()}`)
}
But pages is never present in allSearchParams
what do you mean when you manually add it gets delete
I add ?pages=2
Then I choose a filter, then it's lost
there it is
you left critical info out 😉
in the other action we are ignoring pages
you need to now handle pages separately
post that other action
This one?
async function submitForm(formData: FormData) {
'use server';
const newSearchParams = new URLSearchParams();
allSearchParams.forEach(p => {
const opts = formData.getAll(p)
if (opts.length) {
console.log("Form Data:", p, opts.join(","));
newSearchParams.append(p, opts.join(","));
}
})
redirect(`?${newSearchParams.toString()}`)
}
yeah. so…. the pages part is completely missed here
since there is no “input†for it
This one?
async function submitForm(formData: FormData) {
'use server';
const newSearchParams = new URLSearchParams();
allSearchParams.forEach(p => {
const opts = formData.getAll(p)
if (opts.length) {
console.log("Form Data:", p, opts.join(","));
newSearchParams.append(p, opts.join(","));
}
})
if (initialParams.pages) {
newSearchParams.append("pages", initialParams.pages.toString())
}
redirect(`?${newSearchParams.toString()}`)
}
see what i mean?
Yeah unique case
It is still overwritten when I click a checkbox
Actually I want it to be overwritten
so… good… or bad… haha
Perfect
Do you know why my build might be failing?
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
✓ Generating static pages (6/6)
> Export encountered errors on following paths:
/page: /
not with just that info
This is all it gives me:
✓ Collecting page data
Generating static pages (4/6) [= ]
⨯ useSearchParams() should be wrapped in a suspense boundary at page "/". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
✓ Generating static pages (6/6)
> Export encountered errors on following paths:
/page: /
may seem like nothing to you… but that is very helpful info
Part of this component
'use client'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { useState } from 'react'
const SearchContainer = () => {
// NextJs Navigation
const router = useRouter()
const searchParams = useSearchParams()
const pathname = usePathname()
let queryParams: URLSearchParams;
// React States
const [search, setSearch] = useState(searchParams.get('search') || '')
// Handle Submitting
const handleSubmit = (e: any) => {
e.preventDefault()
if (typeof window !== "undefined") {
queryParams = new URLSearchParams(window.location.search);
}
queryParams.delete("pages");
if (search === '') {
queryParams.delete('search');
} else {
queryParams.set('search', search);
}
const path = window.location.pathname + "?" + queryParams.toString();
router.push(path);
}
const removeSearch = (e: any) => {
router.push(pathname)
return
}
return (
<form className='flex items-center justify-center'>
<input className='border border-gray-400 rounded-md p-1' placeholder='Search' onChange={(e) => setSearch(e.target.value)} defaultValue={search}/>
<button className='border border-gray-400 rounded-md p-1 m-2 hover:bg-blue-100' type='submit' onClick={handleSubmit}>ðŸ”</button>
<button type='submit' onClick={removeSearch}>✖ï¸</button>
</form>
)
}
export default SearchContainer
show where you are using that
I call it in ProductCards.tsx
wrap it in a <Suspense></Suspense>
In the return
but you now know you can probably convert that to a server component in the future
Fixed! Thank you
Yeah, but I am not sure how to deal with something like a searchbar yet
sokay. something to use to learn in your own. gotta have goals
Yup 🙂
Found one last important issue
My search query is deleted when checkbox is clicked
I will try to solve it like the page example
This doesn't work unfortunately:
async function submitForm(formData: FormData) {
'use server';
const newSearchParams = new URLSearchParams();
allSearchParams.forEach(p => {
const opts = formData.getAll(p)
if (opts.length) {
console.log("Form Data:", p, opts.join(","));
newSearchParams.append(p, opts.join(","));
}
})
if (initialParams.search) {
newSearchParams.append("search", initialParams.search.toString())
}
redirect(`?${newSearchParams.toString()}`)
}
Worked without tostring()
that one isnt a number so that would make sense haha
Yup
Does it have to rerender the page every time I click a checkbox? I want to make it so the user can just keep clicking
as a server action yes, but it should not be noticeable in production
But the dropdown closes
whos fault is that ðŸ˜
manage the state of the dropdown
you can do it in another url param, or extract that to a client component
I guess so, but this stuff just worked on the client side
is the ui “just working†any good if the result is that it doesnt do shit? (hence the beginning of this thread)
Thats true
Thanks for all the help today, will likely come back for more soon 😄
it may seem like more work at first, but you are getting granular control over your ui
np man. im gonna pass out soon its late
So I have one last major issue to tackle. Everything works fine in dev, but in build none of my query parameters do anything. It's like they don't exist and they delete existing query params each time
Also, I was thinking - since I serve users a bunch of images each time, won't I have high bandwidth costs when all of those are server side?
I see a "ReferenceError: window is not defined" in the logs
ReferenceError: window is not defined
at n (/Users/berkserbetcioglu/Code/storefront/.next/server/app/[...r]/page.js:1:2996)
at nM (/Users/berkserbetcioglu/Code/storefront/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:47419)
at nN (/Users/berkserbetcioglu/Code/storefront/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:64674)
at nI (/Users/berkserbetcioglu/Code/storefront/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:46806)
at nM (/Users/berkserbetcioglu/Code/storefront/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:47570)
at nM (/Users/berkserbetcioglu/Code/storefront/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:61663)
at nN (/Users/berkserbetcioglu/Code/storefront/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:64674)
at nB (/Users/berkserbetcioglu/Code/storefront/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:67657)
at nF (/Users/berkserbetcioglu/Code/storefront/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:66824)
at nN (/Users/berkserbetcioglu/Code/storefront/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:12:64990)
Marking this as done to move to a new thread
Thanks for the help!
Answer