Skip to content

Commit

Permalink
feat: Add default value
Browse files Browse the repository at this point in the history
See #197.
  • Loading branch information
franky47 committed Mar 9, 2021
1 parent b12b74e commit c67bc13
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 8 deletions.
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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.
2 changes: 1 addition & 1 deletion src/defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
80 changes: 76 additions & 4 deletions src/useQueryState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,98 @@ export interface UseQueryStateOptions<T> extends Serializers<T> {
* The operation to use on state updates. Defaults to `replace`.
*/
history: HistoryOptions
defaultValue: T
}

export type UseQueryStateReturn<T> = [
T | null,
T,
React.Dispatch<React.SetStateAction<T>>
]

export type UseQueryStateOptionsWithDefault<T> = Pick<
UseQueryStateOptions<T>,
'parse' | 'serialize' | 'defaultValue'
> &
Partial<Omit<UseQueryStateOptions<T>, '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<T = string>(
key: string,
options: UseQueryStateOptionsWithDefault<T>
): UseQueryStateReturn<T>

/**
* 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<T = string>(
key: string,
options?: Partial<UseQueryStateOptions<T>>
): UseQueryStateReturn<T | null>

// Implementation --------------------------------------------------------------

export function useQueryState<T = string>(
key: string,
{
history = 'replace',
parse = x => (x as unknown) as T,
serialize = x => `${x}`
serialize = x => `${x}`,
defaultValue
}: Partial<UseQueryStateOptions<T>> = {}
): UseQueryStateReturn<T | null> {
) {
const router = useRouter()

// Memoizing the update function has the advantage of making it
Expand Down Expand Up @@ -99,5 +171,5 @@ export function useQueryState<T = string>(
},
[key, updateUrl]
)
return [value, update]
return [value ?? defaultValue ?? null, update]
}

0 comments on commit c67bc13

Please sign in to comment.