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

Questions about state between nested components #238

Closed
oren opened this issue Jan 2, 2017 · 9 comments
Closed

Questions about state between nested components #238

oren opened this issue Jan 2, 2017 · 9 comments

Comments

@oren
Copy link

oren commented Jan 2, 2017

I have App.html and a child component - Login.html.
When the user submit the login form the Login component calls the server endpoint and on success I change user.loggedIn to true and add a user key to the localStorage. The problem is the App.html should be aware of it so it can change it's content and remove some UI elements (link to login for example).

  1. Is this the right approach?
  2. If SSR and progressive enhancement #1 is 'yes', how do I communicate this with the parent component?
  3. Since I use this router, the new keyword is not being used. How to access the component API from the parent (App.html)?

Here is my example repo:
https://github.com/oren/svelte-router-example/blob/c638c8786084a327bffdcac99da13b55695667cb/src/components/Login.html

Thanks!

@nsaunders
Copy link

In my opinion, the user's logged-in status and key are not UI state and therefore belong outside of your component hierarchy. So, for example, in my own project, I have an "auth" module that contains the session info and exposes functions like logIn, logOut, etc. (For notifying various parts of the UI when the login status changes, I expose a rxjs Observable.) Depending on the complexity of your app, you could also consider using something like Flux or Redux to manage your application state. Anyway, that's just my two cents... Hope it helps.

@Rich-Harris
Copy link
Member

As a general rule (for all component-based architectures, not just Svelte), a piece of state should be stored at the highest component in the tree that's a common ancestor of all components that need to use it, which in this case would be App.html. The tricky part here is that there isn't a great way to pass app-level state down to route-level components.

One option would be to handle routing differently:

{{#if route === '/login'}}
  <Login user='{{user}}'/>
{{elseif route === '/about'}}
  <About/>
{{...and so on}}

It starts to look a bit messy when you have a lot of routes, but it works and is fairly self-explanatory for the next developer.

Handling the state outside the component tree as @therealnicksaunders is also a fine idea (in a way, something like Redux is taking the 'highest component in the tree' mantra to its logical conclusion by putting all data above all components – personally I don't tend to distinguish between UI state and non-UI state, and I like having component-local data, but there's no wrong answer here), it's just a case of adding a bit of subscription boilerplate to onrender and onteardown.

@oren
Copy link
Author

oren commented Jan 4, 2017

Thanks for the replies. I would like to avoid using too many external libraries so I'll try @Rich-Harris's approach first and if I fail I'll resort to something like redux or mobx (since I heard about them and I never used rxjs).

If I use something like this

{{#if route === '/login'}}
  <Login user='{{user}}'/>

does it mean it's replacing my router code?

  const router = createRouter({
    '/': Home,
    '/home': Home,
    '/login': Login,
    '/doctors': Doctors,
    '/about/:name': About,
  });

If anyone got the time, can you show the needed code (gist, modify my code, new sample app, etc).
Thanks!

oren added a commit to oren/svelte-router-example that referenced this issue Jan 4, 2017
@oren
Copy link
Author

oren commented Jan 4, 2017

I think It's working now! - https://github.com/oren/svelte-router-example/blob/9799b6736f1a670286daa34df4cefffdee49d86f/src/App.html

let me know if that's what you meant. I used a helper function:

    helpers: {
      route () {
        return window.location.pathname;
      }
    }

@cayasso
Copy link
Contributor

cayasso commented Jan 4, 2017

Why not use just the url window.location.hash, if you use this then you can register a hashchange event and then act accordingly (do your cleanup stuff). this is what I am using for my simple app.

<!-- Router.html -->
<main ref:router></main>

<script>
  export default {
    onrender() {
      let component = null
      change = change.bind(this)
      window.addEventListener('hashchange', change)

      if (this.to()) change()
      else this.to('home')

      function change() {
        const auth = this.get('auth')
        const to = this.to() || 'home'

        if (auth && auth() === false) {
          if (to !== 'signin') return this.to('signin')
        }

        document.querySelector('html').scrollTop = 0
        document.querySelector('body').scrollTop = 0

        if (component) {
          component.teardown()

          if (component.onleave) component.onleave(to)
        }

        const Component = this.get('routes')[to]

        component = new Component({
          target: this.refs.router
        })

        if (component.onenter) component.onenter(to)
      }
    },
    methods: {
      to(to) {
        if (to) window.location.hash = to
        return location.hash.slice(1)
      }
    }
  }
</script>

And then something like this:

<!-- App.html -->
<Router routes="{{routes}}" auth="{{auth}}" />

<script>
  import Home from './Home.html'
  import Signin from './Signin.html'
  import Router from './Router.html'
  import store from '../store'
  import { getIsAuthenticated } from '../selectors'

  export default {
    data() {
      return {
        routes: {
          'home': Home,
          'signin': Signin
        },
        auth(to) {
          const state = store.getState()
          const isAuthenticated = getIsAuthenticated(state)
          if (!isAuthenticated) return false
        }
      }
    },
    components: {
      Router
    }
  }
</script>

@cayasso
Copy link
Contributor

cayasso commented Jan 4, 2017

BTW. Also notice the onenter and onleave methods that I can add to any component as router lifecycle.

and the other thing is that you can have a global helper like this for jumping between routes.

export function transitionTo(to) {
  window.location.hash = to
}

Its not perfecto but it does its job for a small application without dependencies like mine.

This was inspired by this https://gist.github.com/FWeinb/11e72e8bdfedfb38b40836c9b7f565b9

@nsaunders
Copy link

I'm just going to toss this out there as another alternative... I searched high and low for an existing client-side routing solution that played nicely with Svelte, had a familiar API, and would allow me to modularize my routing configuration (since I prefer to package by feature). Since I couldn't find anything that seemed to fit the bill, I spun up my own routing library, an example of which you can view at https://github.com/sawbladejs/example.

Using this routing library, you should be able to do something like this in order to communicate between your App and Login components (untested):

bootstrap(
  new App({ target: document.getElementById('app') }),
  [
    {
      path: '/login',
      render: ({ parent: app }) => {
        const login = new Login({ target: app.refs.outlet }); // note App needs an "outlet" ref
        login.on('loginSuccess', () => app.removeLoginLink()); // change event and method names as needed
        return login;
      },
      teardown: login => login.teardown()
    }
  ],
  new HashUrlProvider()
);

Anyway, it does introduce a runtime dependency (not as cool as Svelte that way), but I think it scales better than a basic routing implementation using switches (which was the first approach I tried, by the way!).

@oren
Copy link
Author

oren commented Jan 9, 2017

thanks @cayasso and @therealnicksaunders.
@cayasso, i was not able to build a sample app using your approach. I also don't know what 'cleanup stuff' means ):

If you have a working example that would be great. if not, i'll stick to the if/else I wrote above.

@Rich-Harris
Copy link
Member

Will close this as it looks like the issue was resolved

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

No branches or pull requests

4 participants