solidjs / solid-router

A universal router for Solid inspired by Ember and React Router
MIT License
1.15k stars 147 forks source link

Feature request: persist route (don't unmount when navigate out) #204

Open illiaChaban opened 1 year ago

illiaChaban commented 1 year ago

Describe the bug

Not a bug, but no templates for feature requests.

I want to be able to avoid rerendering a previously visited route and the route should keep all the internal state.

"persist?: boolean" prop would be nice Syntax proposal:

<Routes><Route path="/" persist element={...} /> </Routes>

The way I accomplish it right now:

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/stack" />
          ...
        </Routes>
        <PersistRoute path="/stack" component={StackTest} />

export const PersistRoute = (p: { path: string; component: FC }) => {
  const match = useMatch(() => p.path)

  let dispose: () => void | undefined
  const render = once(() => {
    return createRoot(_dispose => {
      dispose = _dispose
      return <p.component />
    })
  })

  onCleanup(() => dispose?.())

  return <Show when={match()}>{render()}</Show>
}

Your Example Website or App

https://github.com/solidjs/solid-router

Steps to Reproduce the Bug or Issue

None

Expected behavior

When has "persist" prop it should mount the component on first visit and not cleanup on navigate out

Screenshots or Videos

No response

Platform

all

Additional context

No response

ryansolid commented 1 year ago

This sort of approach works for now but it is less than ideal since offscreen stuff is "live". I don't think we could make that a general solution. But I have an idea of how to do an "Offscreen" mechanism for the future.

MrFoxPro commented 1 year ago

For now I ended up with this:

function once(fn: Function, onAccess?) {
   let acc = () => {
      const { toReturn, target } = fn!()
      onAccess?.(target)
      acc = () => {
         onAccess?.(target)
         return toReturn
      }
      return toReturn
   }
   return () => acc()
}

function persist(Comp: Component) {
   let routerRoot = document.body

   const [node, setNode] = createSignal<HTMLElement>(null)
   let rendered = false
   function onAccess(val) {
      // Init component
      // This is needed to trigger it hooks
      let output = val()

      let i = 0
      while (output?.constructor === Function) {
         if (i > 10) throw new Error("Something bad happened in routing!")
         output = output()
         i++
      }
      if (output) setNode(output)
   }
   createEffect(() => {
      const n = node()
      if (!n || !routerRoot) return

      if (!rendered) {
         console.log(n, routerRoot.firstChild)
         routerRoot.insertBefore(n, routerRoot.firstChild ?? null)
         rendered = true
      }
      n.style.visibility = "unset"
   })
   function onWrapperShouldUnmount() {
      const n = node()
      if (!n) return

      n.style.visibility = "hidden"
      setNode(null)
   }
   const FakeComponent = () => {
      const comp = <Comp ref={setNode} />
      const target = () => {
         onCleanup(onWrapperShouldUnmount)
         return comp
      }
      return { toReturn: null, target }
   }
   return once(FakeComponent, onAccess)
}
erodriguezds commented 10 months ago

Hi @ryansolid , is there any progress on this, or an alternative solution or pattern? I think this is a valuable feature. Vue has a built-in component for this: keep-alive.

The use-case I have is: we're building a dashboard SPA using Solidjs (btw: best decision we've ever made 😉 ... such a wonderful DX)... we have a page/route where the user can generate reports... they navigate to a different page/module, and when they go back to the report page, everything is cleared, so they have to setup all the filters again and rebuild the reports. This is obviously a bad UX, and the solution other libraries/frameworks offer is something like Vue's <keep-alive> component.

I'd be really nice if Solidjs (or Solid-router?) had a similar feature, or at least, a documented pattern to achieve the same.

Keep on the good work Ryan. Solidjs is the best fvck$ng thing ever happened to the frontend universe, and I tell you so being myself an experienced Vue (2&3) and React developer.

Brendan-csel commented 10 months ago

Could you possibly store the data for the filter/report page globally so it persists independent of the router? Perhaps even in local storage if you want it to persist across page refreshes.

Or if that is not appropriate then use a nested(parent) route to store the data (and provide via context) so it persists for as long as the user is within a subsection of the router tree. We use this approach when we have a pair of list and detail pages and we want to keep the list data around while the user is in the detail page.

ryansolid commented 9 months ago

@erodriguezds keep-alive is a pretty difficult to make the way Solid works today as I was mentioning above. Vue's keep-alive does disconnect side effects and we don't have the means to do this before Solid 2.0. I didn't go out and say it in my response but that was where my idea for "Offscreen" for the future lies.