[NextJS] - Hook para enviar informações para a url via query params.

Nos últimos dias tive uma demanda que consistia em enviar informações para URL, algo relativamente simples, porém no NextJS eu senti uma certa dificuldade para tal. Sendo assim resolvi compartilhar a minha solução para que outros não precisem passar pela mesma experiência.

Obviamente não é a melhor maneira de se fazer e caso alguém tenha outras maneiras de implementar peço que compartilhe, para que assim, possamos aumentar o número de contribuições referente a mesma funcionalidade.

No início pensei em criar um hook simples, que seria uma extensão do useState do ReactJS.

Para começarmos, as seguintes funções são auxiliares para a criação do hook.

Funcão para garantir que o window já foi construido durante a abertura da página. Obs.: Isso é necessário apenas no NextJS, pois o mesmo executa primeiro o nodejs para depois renderizar o reactjs.

function windowExist () {
  return typeof window !== 'undefined'
}

Função para verificar se um objeto está vázio. Ex.: {} = true, {search: "tabnews"} = false

function objectIsEmpty(obj) {
  for (const prop in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, prop)) {
      return false;
    }
  }

  return JSON.stringify(obj) === JSON.stringify({});
}

Função para garantir que o valor primitivo tenha valor e uma chave. Ex.: key: search, data: "tabnews"

const primitives = ['string', 'boolean', 'number']
function sanitizeQueryParamsFromPrimitives(data, query, key) {
  if (!key || !primitives.includes(typeof data)) return;

  query.set(key, String(data));
}

Função para garantir que o objeto enviado tenha valor. Ex.: {search: "tabnews", page: 1}

function sanitizeQueryParamsFromObject(data, query) {
  if (typeof data !== "object" || objectIsEmpty(data)) return;

  Object.keys(data).forEach((key) => {
    if (!objectIsEmpty(data[key])) {
      query.set(String(key), String(data[key]));
    }
  });
}

Implementação do Hook.

// useQueryState.js
import { useState, useEffect } from 'react'
import { useRouter } from 'next/router'

export function useQueryState({ value, key, }) {
  const router = useRouter()
  const [data, setData] = useState(value)
  
  const query = new URLSearchParams(windowExist ? window.location.search : '')

  const dataIsInvalid = data === undefined || data === null

  useEffect(() => {
    if (dataIsInvalid) return
    sanitizeQueryParamsFromPrimitives(data, query, key)
    sanitizeQueryParamsFromObject(data, query)

    router.replace.call(router, {
      pathname: router.pathname,
      hash: windowExist ? window.location.hash : '',
      search: String(query),
    })
  }, [data])

  return [data, setData]
}

Utilização do Hook.

import { useQueryState } from 'sua-pasta'
//...

export default (props) => {
  const [search, setSearch] = useQueryState({
    value: props?.search ?? '', // Garante que se o usuário pesquise algo e salve nos favoritos o search já venha preenchido.
    key: 'search'
  })
  
  // Ex.: Se você digitar tabnews no input a url ficará assim: seu-dominio.com.br?search=tabnews

 // Obs.: Também podem ser implementado lógica que só atualiza o setSearch quando é clicado em um Button.

  return (
    <div>
       <input placeholder="Pesquise" value={search} onChange={e => setSearch(e.currentTarget.value)} />
    </div>
  )
}


export const getServerSideProps = async (ctx) => {
  return {
    props: {
      ...ctx.query
    },
  }
}

Obs.: Críticas construtivas são bem-vindas sempre.