state-in-url
Version:
Store state in URL as in object, types and structure are preserved, with TS validation. Same API as React.useState, wthout any hasssle or boilerplate. Next.js@14-16, react-router@6-7, remix@2, and Astro.
2 lines (1 loc) • 2.36 kB
JavaScript
import i from"react";import{TIMEOUT as A}from"../constants/constants.mjs";import{subscribers as F,stateMap as I}from"../subscribers.mjs";import{useIsomorphicLayoutEffect as H}from"../useIsomorphicLayoutEffect.mjs";import{useSharedState as J}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as K}from"../useUrlEncode/useUrlEncode.mjs";import{isSSR as N,isEqual as W,getSearch as v,popstateEv as M,filterUnknownParamsClient as j,getOtherParams as z,subscribeToUrl as D}from"../utils.mjs";let g;let r;let a;let S;let E=0;const U=()=>window.location.pathname+window.location.search;function f(){a=void 0;S?.();S=void 0}function l(){if(!a)return void 0;const t=U();if(t===a.from&&t!==a.to){return a}f();return void 0}function G(t,o){const n=l()?.from??U();if(n===t){f();return}a={from:n,to:t,search:o};S??=D(f)}function Q(t){if(t.pathname!==window.location.pathname)return;const o=l();const n=t.pathname+t.search;if(o?.to===n)return;const s=n===U();if(s&&!o)return;const p=s?"replace":t.method;t.router[p](t.url,t.opts);G(n,t.search)}function q(){return!!r&&r.pathname===window.location.pathname}function V(t,o,n,s,p=true){const{parse:m,stringify:$}=K(t);i.useState(()=>{if(N||!n||!p)return;const e=!F.get(t).length;if(!e||q()||l())return;const u=n({parse:m});if(!W(I.get(t),u))I.set(t,u)});const{state:B,getState:c,setState:h}=J(t,()=>n?.({parse:m})||t);const d=i.useRef();if(d.current===void 0)d.current=v();H(()=>{const e=()=>{h(m(j(t,v())))};window.addEventListener(M,e);if(d.current!==v())e();return()=>{window.removeEventListener(M,e)}},[]);i.useEffect(()=>{E+=1;return()=>{E-=1;if(E===0)f()}},[]);const w=i.useCallback((e,u)=>{const b=typeof e==="function"?e(c(),t):{...c(),...e};h(b);const T=r?.search??l()?.search??window.location.search;const k=$(b,z(t,T));const C=k.length?`?${k}`:"";const L=X(window.location.pathname,s);const y=`${L}${C}${window.location.hash}`;if(y===`${L}${T}${window.location.hash}`)return;const{replace:P,...R}=u||{};clearTimeout(g);r={router:o,method:P?"replace":"push",url:y,pathname:window.location.pathname,search:C,opts:R};g=setTimeout(()=>{g=void 0;if(!r)return;const x=r;r=void 0;Q(x)},A)},[o,$,c]);const O=i.useCallback(e=>w(t,e),[w]);return{updateState:h,updateUrl:w,state:B,reset:O,getState:c,pendingUrlUpdate:q}}function X(t,o){if(!o||o==="/"){return t}return t.slice(o.length)}export{V as useUrlStateBase};