From c67bc13de4ad2eb6695777708c9751a73d78cac6 Mon Sep 17 00:00:00 2001 From: Francois Best Date: Tue, 9 Mar 2021 21:00:44 +0100 Subject: [PATCH] feat: Add default value See #197. --- README.md | 25 ++++++++++++-- src/defs.ts | 2 +- src/useQueryState.ts | 80 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 99 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 803d0c00..868d7e12 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ useQueryState hook for Next.js - Like React.useState, but stored in the URL quer - 🧘‍♀️ Simple: the URL is the source of truth. - 🕰 Replace history or append to use the Back button to navigate state updates +- ⚡️ Built-in converters for common object types (number, float, boolean, Date) ## Installation @@ -87,6 +88,21 @@ export default () => { } ``` +You can also use the built-in serializers/parsers for common object types: + +```ts +import { queryTypes } from 'next-usequerystate' + +useQueryState('tag') // defaults to string +useQueryState('count', queryTypes.integer) +useQueryState('brightness', queryTypes.float) +useQueryState('darkMode', queryTypes.boolean) +useQueryState('after', queryTypes.timestamp) +useQueryState('date', queryTypes.isoDateTime) +``` + +## Default value + ## History options By default, state updates are done by replacing the current history entry with @@ -112,10 +128,13 @@ Any other value for the `history` option will fallback to the default. ## Caveats Because the Next.js router is not available in an SSR context, this -hook will always return `null` on SSR/SSG. +hook will always return `null` (or the default value if supplied) on SSR/SSG. ## License -[MIT](https://github.com/47ng/next-usequerystate/blob/next/LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com) +[MIT](https://github.com/47ng/next-usequerystate/blob/next/LICENSE) + +- Made with ❤️ by [François Best](https://francoisbest.com) -Using this package at work ? [Sponsor me](https://github.com/sponsors/franky47) to help with support and maintenance. +Using this package at work ? [Sponsor me](https://github.com/sponsors/franky47) +to help with support and maintenance. diff --git a/src/defs.ts b/src/defs.ts index 22bb0175..d5876590 100644 --- a/src/defs.ts +++ b/src/defs.ts @@ -32,7 +32,7 @@ export const queryTypes: QueryTypeMap = { serialize: (v: boolean) => (v ? 'true' : 'false') }, timestamp: { - parse: v => new Date(v), + parse: v => new Date(parseInt(v)), serialize: (v: Date) => v.valueOf().toString() }, isoDateTime: { diff --git a/src/useQueryState.ts b/src/useQueryState.ts index 2127c3fa..16ffc523 100644 --- a/src/useQueryState.ts +++ b/src/useQueryState.ts @@ -7,26 +7,98 @@ export interface UseQueryStateOptions extends Serializers { * The operation to use on state updates. Defaults to `replace`. */ history: HistoryOptions + defaultValue: T } export type UseQueryStateReturn = [ - T | null, + T, React.Dispatch> ] +export type UseQueryStateOptionsWithDefault = Pick< + UseQueryStateOptions, + 'parse' | 'serialize' | 'defaultValue' +> & + Partial, 'parse' | 'serialize' | 'defaultValue'>> + +// Overload type signatures ---------------------------------------------------- + +/** + * React state hook synchronized with a URL query string in Next.js + * + * This variant is used when a `defaultValue` is supplied in the options. + * + * _Note: the URL will **not** be updated with the default value if the query + * is missing._ + * + * Setting the value to `null` will clear the query in the URL, and return + * the default value as state. + * + * Example usage: + * ```ts + * const [count, setCount] = useQueryState('count', { + * ...queryTypes.integer, + * defaultValue: 0 + * }) + * + * const increment = () => setCount(oldCount => oldCount + 1) + * const decrement = () => setCount(oldCount => oldCount - 1) + * const clearCountQuery = () => setCount(null) + * + * // -- + * + * const [date, setDate] = useQueryState('date', { + * ...queryTypes.isoDateTime, + * default: new Date('2021-01-01') + * }) + * + * const setToNow = () => setDate(new Date()) + * const addOneHour = () => { + * setDate(oldDate => new Date(oldDate.valueOf() + 3600_000)) + * } + * ``` + * + * @param key - The URL query string key to bind to + * @param options - Serializers (define the state data type), default value and optional history mode. + */ +export function useQueryState( + key: string, + options: UseQueryStateOptionsWithDefault +): UseQueryStateReturn + /** * React state hook synchronized with a URL query string in Next.js * + * This variant is used without a `defaultValue` supplied in the options. If + * the query is missing in the URL, the state will be `null`. + * + * Example usage: + * ```ts + * // Blog posts filtering by tag + * const [tag, selectTag] = useQueryState('tag') + * const filteredPosts = posts.filter(post => tag ? post.tag === tag : true) + * const clearTag = () => selectTag(null) + * ``` + * * @param key - The URL query string key to bind to + * @param options - Serializers (define the state data type), optional history mode. */ +export function useQueryState( + key: string, + options?: Partial> +): UseQueryStateReturn + +// Implementation -------------------------------------------------------------- + export function useQueryState( key: string, { history = 'replace', parse = x => (x as unknown) as T, - serialize = x => `${x}` + serialize = x => `${x}`, + defaultValue }: Partial> = {} -): UseQueryStateReturn { +) { const router = useRouter() // Memoizing the update function has the advantage of making it @@ -99,5 +171,5 @@ export function useQueryState( }, [key, updateUrl] ) - return [value, update] + return [value ?? defaultValue ?? null, update] }