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

Is it possible to refresh the whole page when changing nested route? #2

Closed
rockingdice opened this issue Feb 12, 2021 · 8 comments
Closed

Comments

@rockingdice
Copy link
Contributor

My routes are organized like this:

class AppRoutes {
  final routes = <QRouteBase>[
    QRoute(
        path: '/main',
        page: (childRouter) => PageMain(router: childRouter),
        children: [
          QRoute(path: '/apps', page: (child) => PageAppManage()),
          QRoute(path: '/home', page: (child) => PageHome()),
          QRoute(path: '/app/:id', page: (child) => PageAppDashboard()),
          QRoute(path: '/app/edit/:id', page: (child) => PageAppEdit()),
          QRoute(path: '/app/cfg/:id', page: (child) => PageAppConfig()),
          QRoute(path: '/app/img/:id', page: (child) => PageAppImage()),
          QRoute(path: '/app/op/:id', page: (child) => PageAppEdit()),
        ]),
    QRoute(path: '/login', page: (childRouter) => PageLogin()),
    QRoute(path: '/404', page: (childRouter) => Placeholder())
  ];
}

From my understanding,
'/main' is responsible for building the PageMain widget, while its nested routes are responsible for building Inner Widgets.

So if I change the :id in the nested URL, the inner widgets get updated, but the PageMain won't.

What I want is to make PageMain also update when the nested URL changes.

I'll try to explain this with an example:
If I navigate from '/main/apps' to '/main/app/1',
The PageAppManage can switch to PageAppDashboard correctly.
But PageMain won't get updated because it is not a part of the nested router, it's the parent router of it.

I can get the param :id within PageMain, but the page won't update if I change the param.

PageMain still needs to be updated in my case, like highlighting the selected item from the menu or change the index of a combo box in the PageMain.

I also thought it could be done not to use the nested route, but it would make the route tree unorganized - the routes are all on the same level, and I have to make each route use PageMain and handle the inner pages accordingly.

BTW, I really appreciate your work on this package. It makes the navigation much much easier than the official one. Thank you!

@SchabanBo
Copy link
Owner

Hi rockingdice,

I'm glad you like the package, you are welcome.
The main purpose was to update a child of widget without changing the whole widget. but of course that is possible too.
you can do that by update the state of PageMain with setState or whatever state management you use.
I did that in the tab example https://routerexample.qlevar.de/#/store/bottomNavigationBar
here

void _onItemTapped(int index) {
setState(() {
// To update the selected tab.
_selectedIndex = index;
});
// To update the page
QR.toName(childrenRoutes[index]);
}

I update first the selected tab within the same widget to update the selected tab.
and then navigate to selected tab to update the child for it.

@rockingdice
Copy link
Contributor Author

Thank you for your detailed reply with the example!

I think the problem is if I use state management instead of using URL to provide params, after reloading the page the state is lost.
There's a known issue about losing the state: flutter/flutter#65777
You can try it from your example, if you choose the second tab and reload the page (hit ctrl/cmd + R on Chrome), the tab selection will be reset to the first tab, but the page is still the second page (the Business one).

The main problem is the missing local state, leading to inconsistent states.

Not only reloading the page, but there could also be another issue:
If you want to save the business page link to browser history and sometimes after to come back to it, the tab bar could not restore to the second tab. You can try it with the link:
example

So I would try my best to avoid using local state management currently (web only). Until the flutter team officially provides a solution to that issue, what I can do is providing useful states only from URL params.

For my case, I want to share the params to both the main router and the sub-router, and if the param is changed, both of them could be updated.

I can get your point about avoiding unnecessary updates for better performance, but still, could there be an option (which is off by default) to update the whole page if the user wants? I think it's probably not a bad design to support more useful scenes.

@SchabanBo
Copy link
Owner

That makes sense.
but I don't have an idea how to update the parent widget. should I recreate the whole widget tree?

I had a similar case with the example and I used the state management for it.
if you navigate to https://routerexample.qlevar.de/#/dashboard/items/details?itemName=Tomatoes
the state management widget will also have the Tomatoes items because I take the init state for the selected item from the params

String selectedItem = QR.currentRoute.params['itemName'].toString();

I hope that could help

@rockingdice
Copy link
Contributor Author

Just an immature idea:
Provide a new option to the QRoute:

QRoute(shouldUpdate: true)

If shouldUpdate is true, then if the URL is matching its child node, the parent node with this option will get updated too.

So in my case :

class AppRoutes {
  final routes = <QRouteBase>[
    QRoute(
        path: '/main',
        page: (childRouter) => PageMain(router: childRouter),
        needUpdate: true,
        children: [
          QRoute(path: '/apps', page: (child) => PageAppManage()),
          QRoute(path: '/home', page: (child) => PageHome()),
          QRoute(path: '/app/:id', page: (child) => PageAppDashboard()),
          QRoute(path: '/app/edit/:id', page: (child) => PageAppEdit()),
          QRoute(path: '/app/cfg/:id', page: (child) => PageAppConfig()),
          QRoute(path: '/app/img/:id', page: (child) => PageAppImage()),
          QRoute(path: '/app/op/:id', page: (child) => PageAppEdit()),
        ]),
    QRoute(path: '/login', page: (childRouter) => PageLogin()),
    QRoute(path: '/404', page: (childRouter) => Placeholder())
  ];
}

If any path is matched below /main, the /main node also gets updated.

But it's just a quick fix, still too rough.

Maybe a custom callback is better:

QRoute(ValueChanged<bool> shouldUpdate)

The user can decide if the node should get updated. Like comparing params changes or others. Mostly based on the URLs:

QRoute(shouldUpdate: () {
    return QR.params.contains('id');
})

Because my PageMain widget uses the id param, it should update when there's one in the params.

@SchabanBo
Copy link
Owner

my problem is with the word Update. with the update, you mean to recreate the entire page?
I don't think that is a good idea, but I have another one.
Instead of QRouter that you get from the page method, you will get a new object named QRouteChild and it will contain the old QRouter to use it in the same way and a call back that will be called every time a child for this route is called. and just pass this new object to your page. and in this callback, you can do what you want.

for example in the PageMain you can do

router.onChildCalled = (child){
  if(child.path == '/app/edit/:id'){
     setState(()[
       selectedChild = 'Edit';
     })
  }
}

something like that what I have in mind

@SchabanBo SchabanBo reopened this Feb 13, 2021
@rockingdice
Copy link
Contributor Author

Seems great! Your way is better. Then the widget won't be recreated, it will be updated conditionally.

@SchabanBo SchabanBo added this to In progress in Router Feb 13, 2021
@SchabanBo SchabanBo moved this from In progress to Done in Router Feb 14, 2021
@SchabanBo SchabanBo added this to To do in Docs Feb 14, 2021
@SchabanBo
Copy link
Owner

So you can now upgrade to 0.3.4 and replace QRouter with QRouteChild and use QRouteChild.childRouter to get the QRouter.
and now if you do this

if you want to save the business page link to browser history and sometimes after to come back to it, the tab bar could not restore to the second tab. You can try it with the link:
example

the selected tab will be correct (if it doesn't work please clear the cash in your browser and try again)
And I did that with just a few lines

// set the init tab from the selected tab.
_selectedIndex = childrenRoutes.indexOf(widget.child.currentChild.name);
// update the selected tab when the child changed
widget.child.onChildCall = () {
setState(() {
_selectedIndex = childrenRoutes.indexOf(widget.child.currentChild.name);
});
};

I hope this fixes your problem 😄

@rockingdice
Copy link
Contributor Author

@SchabanBo I met another problem like this one so I will not make another issue:

Is it possible to refresh the page if navigating to the same route?

The logs are:

Qlevar-Route: Navigate to /main/app-cfg/1
Qlevar-Route: No changes for Key: 12, path: /main/app-cfg/1, Name: /app-cfg/:id was found

So I guess the page will not refresh if the route is the same.

The scenario is: If I click on a link from a menu, I sometimes want to refresh the page state (Like requesting again to get the latest info from the server).
But I don't know how to do it, is there a way to do that already?

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

No branches or pull requests

2 participants