• Stars
    star
    1,407
  • Rank 33,429 (Top 0.7 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 4 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Type-safe search params state manager for Next.js - Like React.useState, but stored in the URL query string.

useQueryState for Next.js

NPM MIT License Continuous Integration Coverage Status Depfu

useQueryState hook for Next.js - Like React.useState, but stored in the URL query string

Features

  • πŸ§˜β€β™€οΈ 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)
  • β™ŠοΈ Linked querystrings with useQueryStates

Installation

$ yarn add next-usequerystate
or
$ npm install next-usequerystate

Usage

import { useQueryState } from 'next-usequerystate'

export default () => {
  const [name, setName] = useQueryState('name')
  return (
    <>
      <h1>Hello, {name || 'anonymous visitor'}!</h1>
      <input value={name || ''} onChange={e => setName(e.target.value)} />
      <button onClick={() => setName(null)}>Clear</button>
    </>
  )
}

Documentation

useQueryState takes one required argument: the key to use in the query string.

Like React.useState, it returns an array with the value present in the query string as a string (or null if none was found), and a state updater function.

Example outputs for our hello world example:

URL name value Notes
/ null No name key in URL
/?name= '' Empty string
/?name=foo 'foo'
/?name=2 '2' Always returns a string by default, see Parsing below

Parsing

If your state type is not a string, you must pass a parsing function in the second argument object.

We provide helpers for common and more advanced object types:

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) // state is a Date
useQueryState('date', queryTypes.isoDateTime) // state is a Date
useQueryState('array', queryTypes.array(queryTypes.integer)) // state is number[]
useQueryState('json', queryTypes.json<Point>()) // state is a Point

// Enums (string-based only)
enum Direction {
  up = 'UP',
  down = 'DOWN',
  left = 'LEFT',
  right = 'RIGHT'
}

const [direction, setDirection] = useQueryState(
  'direction',
  queryTypes
    .stringEnum<Direction>(Object.values(Direction)) // pass a list of allowed values
    .withDefault(Direction.up)
)

You may pass a custom set of parse and serialize functions:

import { useQueryState } from 'next-usequerystate'

export default () => {
  const [hex, setHex] = useQueryState('hex', {
    // TypeScript will automatically infer it's a number
    // based on what `parse` returns.
    parse: (query: string) => parseInt(query, 16),
    serialize: value => value.toString(16)
  })
}

Example: simple counter stored in the URL:

import { useQueryState, queryTypes } from 'next-usequerystate'

export default () => {
  const [count, setCount] = useQueryState('count', queryTypes.integer)
  return (
    <>
      <pre>count: {count}</pre>
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => setCount(c => c ?? 0 + 1)}>+</button>
      <button onClick={() => setCount(c => c ?? 0 - 1)}>-</button>
      <button onClick={() => setCount(null)}>Clear</button>
    </>
  )
}

Default value

When the query string is not present in the URL, the default behaviour is to return null as state.

As you saw in the previous example, it makes state updating and UI rendering tedious.

You can specify a default value to be returned in this case:

const [count, setCount] = useQueryState(
  'count',
  queryTypes.integer.withDefault(0)
)

const increment = () => setCount(c => c + 1) // c will never be null
const decrement = () => setCount(c => c - 1) // c will never be null
const clearCount = () => setCount(null) // Remove query from the URL

Note: the default value is internal to React, it will not be written to the URL.

Setting the state to null will remove the key in the query string and set the state to the default value.

History options

By default, state updates are done by replacing the current history entry with the updated query when state changes.

You can see this as a sort of git squash, where all state-changing operations are merged into a single history value.

You can also opt-in to push a new history item for each state change, per key, which will let you use the Back button to navigate state updates:

// Default: replace current history with new state
useQueryState('foo', { history: 'replace' })

// Append state changes to history:
useQueryState('foo', { history: 'push' })

Any other value for the history option will fallback to the default.

Multiple Queries

Because the Next.js router has asynchronous methods, if you want to do multiple query updates in one go, you'll have to await them, otherwise the latter will overwrite the updates of the former:

const MultipleQueriesDemo = () => {
  const [lat, setLat] = useQueryState('lat', queryTypes.float)
  const [lng, setLng] = useQueryState('lng', queryTypes.float)
  const randomCoordinates = React.useCallback(async () => {
    await setLat(Math.random() * 180 - 90)
    await setLng(Math.random() * 360 - 180)
  }, [])
}

For query keys that should always move together, you can use useQueryStates with an object containing each key's type:

import { useQueryStates, queryTypes } from 'next-usequerystate'

const [coordinates, setCoordinates] = useQueryStates(
  {
    lat: queryTypes.float.withDefault(45.18),
    lng: queryTypes.float.withDefault(5.72)
  },
  {
    history: 'push'
  }
)

const { lat, lng } = coordinates

// Set all (or a subset of) the keys in one go:
await setCoordinates({
  lat: Math.random() * 180 - 90,
  lng: Math.random() * 360 - 180
})

Transition Options

By default Next.js will scroll to the top of the page when changing things in the URL.

To prevent this, router.push() and router.replace() have a third optional parameter to control transitions, which can be passed on the state setter here:

const [name, setName] = useQueryState('name')

setName('Foo', {
  scroll: false,
  shallow: true // Don't run getStaticProps / getServerSideProps / getInitialProps
})

Caveats

Because the Next.js router is not available in an SSR context, this hook will always return null (or the default value if supplied) on SSR/SSG.

License

MIT

Using this package at work ? Sponsor me to help with support and maintenance.

More Repositories

1

chakra-next

Opinionated design system for React, based on Chakra UI + Next.js, written in TypeScript.
TypeScript
217
star
2

prisma-field-encryption

Transparent field-level encryption at rest for Prisma
TypeScript
209
star
3

session-keystore

Secure cryptographic key storage in the browser and Node.js
TypeScript
68
star
4

fastify-cron

Run cron jobs alongside your Fastify server πŸ‘·
TypeScript
47
star
5

cloak

Serialized AES-GCM 256 encryption, decryption and key management in the browser & Node.js
TypeScript
45
star
6

fastify-micro

Opinionated Node.js microservices framework built on Fastify ⚑️
TypeScript
40
star
7

actions-clever-cloud

GitHub Action to deploy to Clever Cloud
TypeScript
37
star
8

check-env

Check that required environment variables are set for your app
TypeScript
20
star
9

simple-e2ee

Simple end-to-end encryption for webapps
TypeScript
11
star
10

typescript-library-starter

Template repository for TypeScript libraries
TypeScript
9
star
11

fastify-ohmysmtp

Fastify plugin for OhMySMTP/MailPace
TypeScript
6
star
12

redact-env

Redact values of critical environment variables in a string
TypeScript
6
star
13

next-cache-explorer

Navigate and debug the Next.js data cache
TypeScript
6
star
14

sceau

Code signing for NPM packages
TypeScript
5
star
15

codec

Universal conversion of Uint8Array from/into UTF-8, base64url and hex in the browser and Node.js
TypeScript
4
star
16

cloak-ui

Companion web UI for @47ng/cloak
TypeScript
4
star
17

opaque

OPAQUE password-authenticated key exchange for Node.js and the browser via WebAssembly
TypeScript
4
star
18

env-alias

Define aliases for environment variables and bind them at runtime
TypeScript
3
star
19

local-state-sync

Persist & sync encrypted app state between browser tabs and pages
3
star
20

strava-auth-cli

Login to the Strava API from the terminal
Rust
2
star
21

otpublish

CLI to generate OTP codes to publish on NPM with 2FA
JavaScript
2
star
22

horcrux

Secret Splitting with Dark Magic πŸ““πŸ’πŸ“ΏπŸ‘‘πŸ†πŸβš‘
TypeScript
2
star
23

47ng.com

Website
TypeScript
1
star
24

tapers

Transform [0;1] <=> [X,Y] values with custom curves
TypeScript
1
star
25

hashdir

Generate a cryptographic view of a directory's contents
Rust
1
star
26

strava_api.rs

Strava API v3 REST API bindings in Rust
Rust
1
star