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

Page that depens on the state #773

Closed
isaribious opened this issue Sep 23, 2022 · 13 comments
Closed

Page that depens on the state #773

isaribious opened this issue Sep 23, 2022 · 13 comments

Comments

@isaribious
Copy link

Hi. I'm try to have a login feature in local go-app/docs. However, screen flickering may occurs when transition pages. Is there a way to avoid this?

As shown below, I use the state management to determine the login state. This code is based on this repo v9.6.4.

package main

func main() {
	app.Route("/login", newLoginPage())

	app.Route("/", newHomePage())				// Assume private page
	app.Route("/getting-started", newGettingStartedPage())	// Assume public page
...
}

type homePage struct {
	app.Compo

	userID string
}

func newHomePage() *homePage {
	return &homePage{}
}

func (p *homePage) OnNav(ctx app.Context) {
	ctx.GetState("userid", &p.userID)

	if p.userID == "" {
		ctx.Navigate("/login")
		return
	}
}

type loginPage struct {
	app.Compo

	name     string
	password string
}

func newLoginPage() *loginPage {
	return &loginPage{}
}

func (p *loginPage) login(ctx app.Context, event app.Event) {
	if p.name != "hoge" || p.password != "foo" { // Quick check
		return
	}

	ctx.SetState("userid", p.name,
		app.Persist,
		app.Encrypt,
		app.ExpiresIn(time.Second*300),
	)

	ctx.Navigate("/")
}

When / page is accessed on the browser without being logged in, the client will redirect to /login page. However, the rendering of / page occurs at least once before it is navigated.

Is it inappropriate to expect that the queued UI goroutines will be canceled by executing Navigate()?

There are risks exposing content without user auth. So, I protect the content in Render().

func (p *homePage) Render() app.UI {
	if p.userID == "" {
		return app.A().
			Href("/login").
			Text("You are being navigated.")
	}

	return ...  // Content that can be viewed after logging in.
}

After logged into / page, the screen flickers when transitioning from other page (ex. /getting-started). It also happens on reload of page. When the component was mounted, all member variables of the component are empty. I want to initialize a member with the state in OnInit(), but there is no way to do it.

If my approach is inappropriate, please correct it.

@maxence-charriere
Copy link
Owner

maxence-charriere commented Sep 23, 2022 via email

@oderwat
Copy link
Sponsor Contributor

oderwat commented Sep 23, 2022

I know it is not very helpful, but those problems are why I do not use the original Go-App routing but my own "master component" that sits on "all routes" and manages the rest. I do not believe in having URLs available the user is not allowed to access in the first place.

If I could not choose that, I would make the / path switch between its content and the login screen instead of having another route for the login. This could go onto all pages. As long as the user is not identified, it shows the login instead.

I don't think your problem is solvable without having the / render unless you suppress it in the render function. It will also render whatever the page will look like in pre-render. It has to render something, after all.

@isaribious
Copy link
Author

Thank you for your advice.

I tried in OnMount(), but screen flickering did not go away as logn as content is switched in Render().

@isaribious
Copy link
Author

@oderwat Thank you for your comment.

For a route like /my-profile including user-specific content, because the content of that page will be different depending on who's looking at it, I believe the state should be used as well as the path to determine what to display.

@oderwat
Copy link
Sponsor Contributor

oderwat commented Sep 23, 2022

@isaribious I created my mountpoint package to swap the component which is used in the page. My state/navigation changes this component as required.

@isaribious
Copy link
Author

@oderwat I touched go-app-pkgs/examples/mountpoint/ and confirmed that there was no screen flickering due to page transitions. However, the flickering occurs when the page is reloaded.

I changed the background color for clarity.

func (c *isolate) Render() app.UI {
	if c.Tabs == nil {
		// return app.Div().Text("Unmounted")
		return app.Div().Style("background-color", "cadetblue").Text("Unmounted")	// Added style
	}
	return app.Div().Body(
		app.A().Style("padding", "5px").Href("#0").Text("Tab 0"),
		app.A().Style("padding", "5px").Href("#1").Text("Tab 1"),
		app.A().Style("padding", "5px").Href("#2").Text("Tab 2"),
		app.Div().Style("padding", "5px").Body(c.mp.UI()),
	)
}

@isaribious
Copy link
Author

@oderwat The mount package works fine if the member variables used to switch content are initialized in OnInit().

This is because a component is initialized before the first rendering is performed.

Change Point:

  1. Upgrade your go-app package to v9.2.0 or later (the version with the initializer interface).
  2. Upgrade your Go modules to v1.18 if necessary
  3. Initialize the Tabs variable in OnInit().

However, in this issue, only this change doesn't effective because the component can't be initialized with a state.

I won't disturb the go-app of design policy, but I tried the following change.

Corrective Action Plan A:

  1. Have the context to OnInit().
  2. Initialize the component with a state in OnInit(Context).
package app

type Initializer interface {
	Composer

	// The function called before the component is pre-rendered or mounted.
-	OnInit()
+	OnInit(Context)
}

func (c *Compo) mount(d Dispatcher) error {
	if c.Mounted() {
		return errors.New("mounting component failed").
			Tag("reason", "already mounted").
			Tag("name", c.name()).
			Tag("kind", c.Kind())
	}

	if initializer, ok := c.self().(Initializer); ok && !d.isServerSide() {
-		initializer.OnInit()
+		initializer.OnInit(d.Context())
	}

	c.disp = d
	c.ctx, c.ctxCancel = context.WithCancel(context.Background())

	root := c.render()
	...
}

func (c *Compo) preRender(p Page) {
	c.root.preRender(p)

	if initializer, ok := c.self().(Initializer); ok {
-		initializer.OnInit()
+		c.dispatch(initializer.OnInit)
	}

	if preRenderer, ok := c.self().(PreRenderer); ok {
		c.dispatch(preRenderer.OnPreRender)
	}
}
package main

type homePage struct {
	app.Compo

	userID string
}

func (p *homePage) OnInit(ctx app.Context) {
	ctx.GetState("userid", &p.userID)
}

I made sure that the component be able to initialize with the state, and screen flickering have go away by this change.

@oderwat
Copy link
Sponsor Contributor

oderwat commented Sep 26, 2022

Besides, it is not me who decides anything about go-app: OnInit() is meant to be called before a context exists for the component. You call it with the dispatcher's context. That may be OK for getting the state, but I prefer not to change OnInit() as this will break the existing code. It may be possible to get the dispatcher's context in another way.

You could leave everything as is and make the user id a global variable. No need to use the state for that.

Besides that, you can check in Render() if the component is mounted/initialized and skip rendering with some placeholder if it is not. I do this a lot.

I also do not care about "reloading" a PWA. I avoid anything like that because it breaks everything you set up as an internal state for the app. It is like restarting your computer. The magic can work, but it needs a lot more than just knowing the URL in our cases.

P.S.: Most of the time, I get the impression that people trying Go-App are using it for something that is merely a regular website with the declarative HTML appeal. I think of Go-App only in terms of SPA and would not load a big chunk of web assembly for a mundane HTML site I can do with any other web framework.

@maxence-charriere
Copy link
Owner

Well I made go-app so search engines think it is normal webpage but @oderwat is totally right. It works as a SPA.

@isaribious
Copy link
Author

@oderwat Thank you for your valuable opinion.

I understand. I'll withdraw my proposal. In the first place, it may be wrong to try to do something like a redirect in the component.

@isaribious
Copy link
Author

Besides that, you can check in Render() if the component is mounted/initialized and skip rendering with some placeholder if it is not. I do this a lot.

@maxence-charriere Do frequently mentioned murlok use the go-app's state?

Is this site based on the idea of rendering some placeholder (which doesn't include user-specific content) without redirecting to another page if it's not authenticated?

@maxence-charriere
Copy link
Owner

Murlok.io uses go-app states and change its content depending of if the user is logged in, without page redirection.

@isaribious
Copy link
Author

@maxence-charriere Thank you so much for your reply.

This time, I've come to the conclusion that placeholders in the component should not be used for the purpose of redirecting a page.

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

3 participants