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

Reloading current route #126

Open
felipedrumond opened this issue Mar 22, 2019 · 22 comments
Open

Reloading current route #126

felipedrumond opened this issue Mar 22, 2019 · 22 comments

Comments

@felipedrumond
Copy link

Is there any way to reload the current route? I know I can just not use event.preventDefault() but that reloads the app as a whole.

@TehShrike
Copy link
Owner

What's your use case?

In general, anything that should cause your states to reload should be represented in the route.

I'd need to know more about your specific use case to give better advice.

What is changing that makes you want to re-run all the resolves (I assume that's the motivation)?

@felipedrumond
Copy link
Author

The idea is to enter the route and reload the specified template even though the user is already in that route.

@TehShrike
Copy link
Owner

Right, I got that, but why? What is happening in your application that makes that a thing you want to do?

@felipedrumond
Copy link
Author

Allow users to reload the template by navigating to the same url address. In Angular we can either set the route to not be reused or configure a behaviour to onSameUrlNavigation.

@TehShrike
Copy link
Owner

Why do you want users to be able to reload the page? What is going on that makes a reload a good idea? Is it something in a resolve function that you want to happen again?

@felipedrumond
Copy link
Author

I want them to restart the component without having a 'restart button'. Not sure this is achievable by configuring the states or resolves.

@TehShrike
Copy link
Owner

Why do you want them to restart the component? What is your app doing? Why does the component need to be restarted?

@TehShrike
Copy link
Owner

This is inevitably tied to your specific application needs - I would really recommend joining the chat and chatting with other ASR users about how you should accomplish what you need

@TehShrike
Copy link
Owner

I'm turning around on this idea – I think there are good use cases for reloading some of the current states, and while talking to @saibotsivad the other day, I thought of a way to implement it.

State changes are driven by the browser's location right now, and the go method works by changing the location, and trusting that to trigger the state change.

I don't want to mess with that paradigm, so I wouldn't tie this reload-without-a-location-change functionality to go().

But maybe there should be a new reload or refresh (give name ideas plz) method that takes a list of parameters, and causes all states to be reloaded as if that parameter had changed. e.g. if you know that the company object you were viewing had changed and you wanted to reload all the views that would be affected by that change, you would call stateRouter.actAsThoughThisChanged([ 'companyId' ]).

Thoughts?

@daytonlowell
Copy link
Contributor

As far as naming goes, there is precedence for reload on the web (see document.location.reload()).

@saibotsivad
Copy link
Collaborator

Suppose that I'm at a state like app.schoolId.teams.list.published and something I do changes the school object which is loaded in the resolve of app.schoolId.

I think I would like to be able to re-run the resolve etc. functions of app.schoolId on up the tree, instead of e.g. re-running all the resolve functions.

However, if it re-ran all the resolves etc., that would still be okay, the asr user (me!) would be able to implement some server or browser side caching, to make sure the reloading didn't take too long.

@daytonlowell
Copy link
Contributor

I currently use a mediator for this concept.

So in this example, the app.schoolId.teams.list.published state would do something like mediator.publish('school-change', school) and all the states that care about school would catch the change mediator.provide('school-change', school => domApi.set({ school }))

@saibotsivad
Copy link
Collaborator

That's essentially what I'm starting to work toward as well.

Getting it started has felt kind of clunky, but it's pretty clear that after I make a handful, some obvious tooling will present itself that will make it cleaner and easier.

@daytonlowell
Copy link
Contributor

Unfortunately, newer versions of mannish don't support the concept of multiple providers handling the same named function. Thus, I'm still on an older version.

@saibotsivad
Copy link
Collaborator

I started developing my tooling for this using this emitter.

There's a little work with making sure to unsubscribe the emitter when a component is destroyed, etc., but it's been pretty good so far. 🤞

@TehShrike
Copy link
Owner

I think I would like to be able to re-run the resolve etc. functions of app.schoolId on up the tree, instead of e.g. re-running all the resolve functions.

@saibotsivad that's what I'm aiming for – presumably, that state depends on a schoolId parameter, so if you called stateRouter.actAsThoughThisChanged([ 'schoolId' ]), ASR should only re-resolve/render the states that would have been re-run if you changed the schoolId in the url.

@TehShrike
Copy link
Owner

I'm feeling pretty good about this feature so far. We can keep workshopping the name. In the meantime, if anyone wanted to write a unit test or two, I'd much appreciate it.

A test could start with a copy/paste from https://github.com/TehShrike/abstract-state-router/blob/master/test/test.js changed to assert:

  1. beforeResetState should be emitted for every state that should be getting reset (if schoolId changed at app.school.whatevs, then app.school and app.school.whatevs should be reset)
  2. resolve and activate should be called for each of those functions

You can use whatever dummy method name you like for now in the tests.

@sw-clough
Copy link

sw-clough commented Oct 23, 2019

I am in need of this functionality. As an example of what I am trying to do, I have route foo that shows the title and has tabs for the child states. The default child is foo.view, which shows some other info, and foo.edit. Upon doing an edit, which might change the title, I need to reload the foo route, to see the new title.

TBH, I would be happy if I could just do something like go("foo", {}, {force: true}). Right now, this refreshes foo.view, but does not refresh foo. Would this be quicker and easier to implement?

@TehShrike
Copy link
Owner

@sw-clough that's a good use case to note – where you are actually doing a state change, but you need a parent of the changed state to reload as well. It would be good to support that too.

In your case, is foo dependent on a particular parameter to load the title of the thing?

Can you give your concrete example instead of using foo?

I don't want to do a quick/easy implementation – I'm pretty sure this functionality can be implemented in a way that fits in nice with the current ASR paradigms, where we just say "this is the browser navigation that should happen, and also for the purpose of state changes, these parameters should be treated as if they had changed as well".

@saibotsivad
Copy link
Collaborator

@sw-clough This might not work for you, but I'll show you one of the ways that I manage updates. I leave it here in hopes that it helps you, or someone else.

In my application, I create a global emitter:

// emitter.js
const createEmitter = require('better-emitter')
const emitter = createEmitter()
module.exports = emitter

Then in the child view, e.g. foo.view, when the object changes I emit that change:

// foo-view.js
const emitter = require('./emitter.js')
//...
asr.addState({
    name: 'foo.view',
    activate: ({ domApi }) => {
        domApi.on('change', title => {
            emitter.emit('change-title', title)
        })
    }
})

And in any view that cares about that property, I watch for those changes (be careful to not leave event emitter listeners lying around):

// foo.js
const emitter = require('./emitter.js')
//...
asr.addState({
    name: 'foo',
    activate: ({ domApi }) => {
        const unsubscribe = emitter.on('change-title', title => {
            domApi.set({ title })
        })
        domApi.on('destroy', () => {
            if (unsubscribe) {
                unsubscribe()
            }
        })
    }
})

In practice, I've made some wrapper functions so I can register a component for changes, and handle the subscribe/unsubscribe functionality without a lot of repeated code.

There may be easier ways to manage state inside your framework (for example "stores" in Svelte3), but something along these lines:

// state.js
const emitter = require('./emitter.js')
module.exports = {
    subscribe: (domApi, key, cb) => {
        const unsubscribe = emitter.on(key, props => {
            cb(props)
        })
        domApi.on('destroy', () => {
            if (unsubscribe) {
                unsubscribe()
            }
        })
    }
}

And then in the watching state:

// foo.js
const { subscribe } = require('./state.js')
//...
asr.addState({
    name: 'foo',
    activate: ({ domApi }) => {
        subscribe(domApi, 'change-title', title => domApi.set({ title }))
    }
})

@daytonlowell
Copy link
Contributor

daytonlowell commented Jan 28, 2022

A .reload method makes sense to me. That said, I do agree with @sw-clough that a force-like option you could pass to .go would be pretty handy. Without that, my code would have to figure out if I need to call .go or .reload in certain instances, which would involve looking at the current state name and the parameters and comparing them to the "destination" state to see if they're different. That's a lot of overhead that I'd rather the state router abstract away from me.

Here's an example use case for me:
I have a search view. The parent state has form input controls where they can create a search query. The child state is the results. The results state takes parameters that are passed from the parent.

The user can perform certain operations on the results that might change which results should be shown. Also the user could leave the results open for days and decide to reperform the search when they return(data could have changed on the server). Currently to do this, they have to either go to a different state and return, or refresh the browser(entire web app), neither of which is a great solution.

My current workaround is to make a parameter be a uuid and generate a new one on each .go call for this state.

@TehShrike
Copy link
Owner

That argument for baking it into go makes sense to me.

You would only want the results state to be reloaded in that case, right? You wouldn't want every parent state to reload and re-render?

I'm imagining that rather than a boolean, go could take another option that would be an array of property names about which you wanted to say "act as if all of these parameters changed in the url, even if they're actually the same value".

You could pass in the names of all of the parameters that are relevant to the search results, and the search results would always be refreshed even if the values hadn't changed.

Alternately, if you only wanted the results state to reload when you're at app.search.results, you could pass in a parameter like app.search to say "reload all descendants of this state, even if their parameters haven't changed".

This might be easier/nicer in some circumstances, but I don't feel as good about it, because it kind of steps around the "the parameters drive the states" model.

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

No branches or pull requests

5 participants