Skip to content

Instantly share code, notes, and snippets.

@swyxio
Last active July 26, 2022 10:43
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save swyxio/efd9ee71669413bca6a895d87e30742f to your computer and use it in GitHub Desktop.
Save swyxio/efd9ee71669413bca6a895d87e30742f to your computer and use it in GitHub Desktop.
Sync your state to your query params for copy-pastable state-in-URLs. React Router, Gatsby, and Svelte versions
// note - this was my rough working prototype, but still left some artifacts in the query params.
// todo: also need to debounce the history pushes
// see comments for production Gatsby and Svelte versions which delete nondefault keys in the query params
import React from 'react'
import queryString from "query-string"
export const connectRouterState = defaultURLState => Component => props => {
const { history, location } = props
// note: do not nest objects in URL state
const urlState = { ...defaultURLState, ...queryString.parse(location.search) }
const setURLState = newState =>
history.push({ search: `?${queryString.stringify({ ...urlState, ...newState })}` })
return <Component
setURLState={setURLState}
urlState={urlState}
{...props}
/>
}
// usage example - assumes this connected component is provided `location` and `history` by react-router
class MyApp extends Component {
render() {
const { urlState, setURLState } = this.props
return (
<div>
<button onClick={() => setURLState({ newState: [1, 2] })}>{JSON.stringify(urlState)}</button>
</div>
)
}
}
export default connectRouterState({ defaultState: 1 })(MyApp)
@swyxio
Copy link
Author

swyxio commented Jun 17, 2018

this is what im using in production - has some app specific logic with regard how it drops values:

import React from 'react'
import queryString from "query-string"

// manage your state entirely within the router, so that it's copiable
// https://gist.github.com/sw-yx/efd9ee71669413bca6a895d87e30742f

export default defaultURLState => Component => props => {
  const { history, location } = props
  const urlState = { ...defaultURLState, ...queryString.parse(location.search) }
  const setURLState = newState => {
    const finalState = { ...urlState, ...newState } // merge with existing urlstate
    Object.keys(finalState).forEach(function (k) {
      if ( // don't save some state values if it meets the conditions below
        !finalState[k] || // falsy
        finalState[k] === "" || // string
        (Array.isArray(finalState[k]) && !finalState[k].length) || // array
        finalState[k] === defaultURLState[k] // same as default state, unnecessary
      ) {
        delete finalState[k]; // drop query params with new values = falsy
      }
    });
    return history.push({ search: `?${queryString.stringify(finalState)}` })
  }
  return <Component
    setURLState={setURLState} // use this instead of `setState`
    urlState={urlState} // easier to read state from this instead of `location`
    {...props}
  />
}

@theKashey
Copy link

  1. Make it renderprop
  2. Put into Context
  3. ...
  4. Profit!

@swyxio
Copy link
Author

swyxio commented Aug 16, 2019

2019:

  1. make it a hook
  2. ...
  3. profit!

@baruchiro
Copy link

Hi, I created a custom hook for that, can you share you feedback please?
https://github.com/baruchiro/use-route-as-state

@swyxio
Copy link
Author

swyxio commented Sep 23, 2020

my svelte code today

  import queryString from "query-string"
  import {onMount} from 'svelte'

  let urlState = {filter: '' }
  let defaultURLState = { filter: '' }
  onMount(() => {
    urlState = { ...defaultURLState, ...queryString.parse(location.search) }
  })
  
  const setURLState = newState => {
    const finalState = { ...urlState, ...newState } // merge with existing urlstate
    Object.keys(finalState).forEach(function (k) {
      if ( // don't save some state values if it meets the conditions below
        !finalState[k] || // falsy
        finalState[k] === "" || // string
        (Array.isArray(finalState[k]) && !finalState[k].length) || // array
        finalState[k] === defaultURLState[k] // same as default state, unnecessary
      ) {
        delete finalState[k]; // drop query params with new values = falsy
      }
    });
    urlState = finalState // sync queryparam state to svelte component state
    if (typeof window !== 'undefined') history.pushState({},'', document.location.origin + document.location.pathname + '?' + queryString.stringify(finalState))
  }
  let filterStr = ''
  $: setURLState({filter: filterStr })

  

TODO: Debounce

@swyxio
Copy link
Author

swyxio commented Sep 23, 2020

can apply this debounce code but it makes the state update a little slow

  function debounce(func, wait = 200) {
    var timeout;
    return function executedFunction() {
      var context = this;
      var args = arguments;
      var later = function() {
        timeout = null;
      };
      var callNow = !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
      if (callNow) func.apply(context, args);
    };
  };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment