Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stateful Components Re-used Across SPA "pages" #234

Closed
NfNitLoop opened this issue Aug 1, 2021 · 4 comments
Closed

Stateful Components Re-used Across SPA "pages" #234

NfNitLoop opened this issue Aug 1, 2021 · 4 comments
Labels
question Further information is requested

Comments

@NfNitLoop
Copy link

NfNitLoop commented Aug 1, 2021

So, in Svelte a lot of the logic seems to be focused on top-down data passing and HTML rendering. Top-level components pass variables to inner components and inner components (re)render their contents based on the variables they're passed.

And Svelte optimizes for this use case. If I've got:

<script>
let value = 42;
</script>

<SomeComponent {value} />
<button on:click={() => value += 1}>Click</button>

then:

  1. Svelte initializes SomeComponent with value=42
  2. User clicks the button.
  3. Svelte does not initialize SomeComponent, but re-uses the existing instance
  4. Svelte updates its value to 43 and the component re-renders itself.

This works well for the top-down data flow, but not so much when you have components with state that does not derive directly from the values they were passed. I've bumped up against this a couple times using svelte-spa-router.


So here's my scenario:

  • A user is viewing a page that shows a post, and a <CommentEditor />
  • User enters some text. CommentEditor updates its internal state depending on that text.
  • User clicks the "Post" button. The comment gets successfully posted to the server.
  • I use push() to navigate to a new page for that comment. (It's the same route as the page I'm currently on, just with different route :ids.)
  • Svelte just sees this as variable updates at the top-level of the page.
  • Svelte re-uses <Route>
  • <Route> re-uses an existing Page component and passes it new params.
  • Updated params get passed to my CommentEdtor
  • ... but the user-entered text does not depend on those params, so it remains, even though we've conceptually navigated to a new page.
  • So now my user has navigated to a new page, and his comment from a previous page is showing up there.

I've found a couple workarounds that work:

  1. I can make a reactive assignment in my CommentEditor to reset its state when its properties change. (Though, then I wrote a nice bug for myself because I didn't fully understand reactive assignment precedence/ordering. And it seems error-prone to need to do this to all stateful components in your app.)

  2. I can wrap my Page component in a big {#key $location} block to force all components to be re-initialized when the page changes. (But I need to remember to do this on every page-level component.)

Is there some better pattern for this?

I keep thinking of these views as "pages", so I guess I expected component initialization when I navigate to a new "page". Is this something that should/could be built into svelte-spa-router?

@NfNitLoop
Copy link
Author

(But I need to remember to do this on every page-level component.)

Ah, it looks like I can just use a key on my top-level route:

{#key $location}
    <Router {routes}/>
{/key}

But my main questions remain:

  1. Is this the best pattern to use for this case?
  2. Should this be built-in to svelte-spa-router. (Or, maybe at least documented?)

@ItalyPaleAle
Copy link
Owner

You're correct that this is how Svelte works, by design. They try to minimize the time that components are mounted and un-mounted. It's not as much a behavior of svelte-spa-router, but more of a behavior of Svelte itself.

I don't know if I would do this:

{#key $location}
    <Router {routes}/>
{/key}

While it may work, what concerns me is that this may not scale if you need to add more keys. Right now you have one, but what about the future?

If I understood your situation correctly, what I would personally do is... Just delete the state from CommentEditor after a comment is saved on the server. I don't know how CommentEditor is implemented, but I assume there's a fetch call somewhere in there: attach a then to that so when the call is complete, you clear the state of CommentEditor manually.

What do you think?

@ItalyPaleAle ItalyPaleAle added the question Further information is requested label Aug 2, 2021
@NfNitLoop
Copy link
Author

NfNitLoop commented Aug 3, 2021

Just delete the state from CommentEditor after a comment is saved on the server.

That's basically my # 1 workaround. (CommentEditor doesn't submit its own comment, but it can detect when it's being rendered on a new page by detecting when some of the variables it was passed have changed and clearing itself.)

It works. It's just a non-obvious problem to run into.

when the call is complete, you clear the state of CommentEditor manually.

This would work too. It's just not clear that it's necessary because conceptually, I'm just going to navigate away from this SPA "page" and render a new one.

what concerns me is that this may not scale if you need to add more keys. Right now you have one, but what about the future?

I can still add {#key ...} blocks in the components inside of Router as usual. This one takes care of my main issue which is: I expect navigating to a new page to start with newly initialized components. (At least for components "inside" of <Router>'s tree, things I'm thinking of as "pages". Not for things at the top-level app "outside" of <Router>.)

I could see it maybe being a performance issue if you've got a lot of stateless components that now need to be reinitialized? But for my use case I can't see a performance difference so far.

@ItalyPaleAle
Copy link
Owner

Thanks for confirming. Going to close this for now as something by-design and somehow app-specific. This conversation can be useful to others in the future however :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants