Last active
November 21, 2022 09:26
-
-
Save jokull/959d8c5dcdd7b10711666fb61177e778 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable @typescript-eslint/no-unsafe-call */ | |
/* eslint-disable @typescript-eslint/no-unsafe-return */ | |
/* eslint-disable @typescript-eslint/no-explicit-any */ | |
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ | |
/* eslint-disable @typescript-eslint/no-unsafe-argument */ | |
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ | |
/* eslint-disable react-hooks/exhaustive-deps */ | |
import { useRouter } from "next/router"; | |
import { z } from "zod"; | |
export function isZodType( | |
t: z.ZodTypeAny, | |
type: z.ZodFirstPartyTypeKind | |
): boolean { | |
if (t._def?.typeName === type) { | |
return true; | |
} | |
if ( | |
t._def?.typeName === z.ZodFirstPartyTypeKind.ZodEffects && | |
(t as z.ZodEffects<any>)._def.effect.type === "refinement" | |
) { | |
return isZodType((t as z.ZodEffects<any>).innerType(), type); | |
} | |
if (t._def?.innerType) { | |
return isZodType(t._def?.innerType, type); | |
} | |
return false; | |
} | |
export function withoutTransform(t: z.ZodTypeAny): z.ZodTypeAny { | |
if (t._def?.typeName === z.ZodFirstPartyTypeKind.ZodEffects) { | |
return withoutTransform((t as z.ZodEffects<any>).innerType()); | |
} | |
return t; | |
} | |
function validateParam<T extends z.ZodDefault<z.ZodTypeAny>>( | |
schema: T, | |
parameter: string | null | |
): z.infer<T> { | |
let processed; | |
if ( | |
(isZodType(schema, z.ZodFirstPartyTypeKind.ZodNumber) || | |
isZodType(schema, z.ZodFirstPartyTypeKind.ZodBoolean)) && | |
parameter && | |
typeof parameter === "string" | |
) { | |
processed = z.preprocess<typeof schema>((x) => { | |
try { | |
return JSON.parse(x as string); | |
} catch { | |
return x; | |
} | |
}, schema); | |
} else { | |
processed = schema; | |
} | |
try { | |
const parsed: z.infer<T> = processed.parse(parameter); | |
return parsed; | |
} catch (error) { | |
return schema._def.defaultValue(); | |
} | |
} | |
function useSearchParam<T extends z.ZodDefault<z.ZodTypeAny>>( | |
key: string, | |
schema: T | |
): [z.infer<T>, (newValue: string | z.infer<T>) => void] { | |
const router = useRouter(); | |
let searchParams: URLSearchParams; | |
if (router.isReady && router.asPath.includes("?")) { | |
searchParams = new URLSearchParams(router.asPath.split("?", 2).at(1)); | |
} else { | |
searchParams = new URLSearchParams(); | |
} | |
console.log({ searchParams: searchParams.toString() }); | |
const value = validateParam( | |
schema, | |
searchParams.get(key) ?? schema._def.defaultValue() | |
); | |
function setValue(newValue: string | z.infer<T>) { | |
const newSearchParams = new URLSearchParams(searchParams); | |
const validated = validateParam(schema, newValue); | |
const stringified = | |
isZodType(schema, z.ZodFirstPartyTypeKind.ZodNumber) || | |
isZodType(schema, z.ZodFirstPartyTypeKind.ZodBoolean) | |
? JSON.stringify(validated) | |
: newValue; | |
console.log({ | |
newValue, | |
newSearchParams: newSearchParams.toString(), | |
validated, | |
stringified, | |
defaultValue: schema._def.defaultValue(), | |
isEqualDefault: newValue === schema._def.defaultValue(), | |
}); | |
if (newValue === schema._def.defaultValue()) { | |
newSearchParams.delete(key); | |
} else { | |
newSearchParams.set(key, stringified); | |
} | |
void router.push(`${router.pathname}?${newSearchParams.toString()}`); | |
} | |
return [value, setValue]; | |
} | |
export default function Page() { | |
const [name, setName] = useSearchParam("name", z.string().default("")); | |
const [id, setId] = useSearchParam("id", z.number().default(1)); | |
const [nullableId, setNullableId] = useSearchParam( | |
"nullableId", | |
z.number().nullable().default(1) | |
); | |
const [mobile, setMobile] = useSearchParam( | |
"mobile", | |
z.boolean().default(true) | |
); | |
return ( | |
<div className="m-12 mx-auto max-w-md"> | |
<div className="mb-8 flex flex-col gap-4"> | |
<div> | |
<strong>name</strong>: {name} is {typeof name} | |
</div> | |
<div> | |
<strong>id</strong>: {id} is {typeof id} | |
</div> | |
<div> | |
<strong>mobile</strong>: {String(mobile)} is {typeof mobile} | |
</div> | |
<div> | |
<strong>nullableId</strong>: {String(nullableId)} is{" "} | |
{typeof nullableId} | |
</div> | |
</div> | |
<div className="flex flex-col items-start gap-4"> | |
<input | |
value={name} | |
onChange={({ target }) => { | |
setName(target.value); | |
}} | |
className="rounded-lg border px-2 py-1" | |
/> | |
<button | |
className="rounded-lg border px-2 py-1" | |
onClick={() => { | |
setId(id + 1); | |
}} | |
> | |
Increment id | |
</button> | |
<div className="flex gap-4"> | |
<button | |
className="rounded-lg border px-2 py-1" | |
onClick={() => { | |
setNullableId( | |
typeof nullableId === "number" ? nullableId + 1 : 1 | |
); | |
}} | |
> | |
Increment nullable id | |
</button> | |
<button | |
className="rounded-lg border px-2 py-1" | |
onClick={() => { | |
setNullableId(null); | |
}} | |
> | |
Nullify nullableId | |
</button> | |
</div> | |
<button | |
className="rounded-lg border px-2 py-1" | |
onClick={() => { | |
setMobile(!mobile); | |
}} | |
> | |
Flip | |
</button> | |
</div> | |
</div> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment