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

Replace state on client happens after beforeEach router hook #105

Open
oshnix opened this issue Jul 13, 2017 · 10 comments
Open

Replace state on client happens after beforeEach router hook #105

oshnix opened this issue Jul 13, 2017 · 10 comments

Comments

@oshnix
Copy link

oshnix commented Jul 13, 2017

I want some unauthorized users to prevent from visiting pages on site.
I added beforeEach hook for each route that looks like

//main.js
router.beforeEach((to,from,next) => {
	let authorized = store.state.user !== null;
        if(to.matched.some(record => record.meta.auth === true)){ 
    		if(!authorized){
			    next({ name: 'Error', params: [to.path], replace: true });
		    }
	    }
       next();
});

And it works for me just fine but when it comes to user side it looks like this hook work before replacing local store state with server side fetched data.
So the page always redirects me to error and I get plenty of warnings about DOM tree not matching server rendered DOM.
The only idea I have is to replace state just after creating store so createApp function will look something like this:

export function createApp() {
	const store = createStore();
	if(SERVER === false && window.__INITIAL_STATE__){
		store.replaceState(window.__INITIAL_STATE__);
	}
	const router = createRouter();
	router.beforeEach((to,from,next) => {
		let authorized = store.state.user !== null;
		if(to.matched.some(record => record.meta.auth === true)){
			if(!authorized){
				next({ name: 'Error', params: [to.path], replace: true });
			}
		}
		next();
	});
	const app = new Vue({
		router,
		store,
		render: h => h(App)
	});
	return {app, router, store};
}

But it doesn't look good. If there's a way to do it right?

@Rufio2031
Copy link

This is the exact issue I've been trying to figure out as well. I would love to get some help with this.

@oshnix
Copy link
Author

oshnix commented Dec 1, 2017

@Rufio2031 I am still waiting for an answer but my example works perfect for me

@Rufio2031
Copy link

Thanks @oshnix , I'll try that out tonight. This seems like this is such a fundamental issue but I'm having a hard time finding other people with the same issue and when I do, it seems they also don't have answers either.

Does your main.js have access to window? If I remember correctly I'd have to pass it in to the createApp factory method because I couldn't reference it from in there.

@oshnix
Copy link
Author

oshnix commented Dec 4, 2017

@Rufio2031 That's a strange question because window property is global in all browsers and you should have access to it from any function until it was shadowed.
As for server side you do not have window there at all.
If you have some problems with accessing window from createApp can you describe it?

@Rufio2031
Copy link

@oshnix sorry I thought I followed up but I guess I didn't. I was able to get this to work following your code and I wanted to thank you for sharing your solution. At this point I don't care how good it looks :)

You're right, I was able to access window from my createApp. I may have wrongly suggested I couldn't access window. I know I couldn't at one point but maybe I was trying it from server side.

@MicroDroid
Copy link

MicroDroid commented Jan 17, 2018

I just used typeof(window) !== 'undefined' instead of SERVER === false.

However, I think the best solution is to create the router itself inside the createApp function and then make that function take an argument, say, initialState so that it uses that as initial state before creating anything else. So your client entry would provide window.__INITIAL_STATE__ and your server one would just provide null or something for no defined initial state

@oshnix
Copy link
Author

oshnix commented Jan 18, 2018

@MicroDroid Good thing about SERVER === false is that SERVER is webpack-defined variable so it will be replaced with true or false during build. Usually it's name is something like env.production or something like this, it was just bad naming in my example.
I do not clearly understand your proposal because it looks just like mine, except the fact that you adviced to pass window.__INITIAL_STATE__ as createApp argument.

@MicroDroid
Copy link

@oshnix I was saying that instead of defining the SERVER webpack variable, you could just simply check for the existence of window since it's only available on client-side. Though not so right when you mock a window for server-side part. This is what I currently use but it's not the best solution in my opinion.

I find giving createApp an initialState argument is a good solution, so that your entry-client.js is like:

createApp(window.__INITIAL_STATE__);`

And your server one is:

createApp(null);

where passing null as initialState would just be the equivalent of not replacing the state at all.

@ozguruysal
Copy link

Alternatively you can do as follows:

// app.js

export default function createApp() {
  const store = createStore();
  const router = createRouter(store); // <--- pass `store` to `createRouter` function

  const app = new Vue({
      router,
      store,
      render: h => h(App),
  });

  return { app, router, store };
}
// router.js

export default function createRouter(store) {
  const router = new Router({
    mode: "history",

    routes,
  });

  router.beforeEach((to, from, next) => {
    // In the client side get the state from window.__INITIAL_STATE__.
    // Because it's not available in client side store at this moment.
    const { user } = process.browser ? window.__INITIAL_STATE__ : store.state;

    // user is available on both client and server side now
    next();
  });

  return router;
}

@vovopap
Copy link

vovopap commented Nov 8, 2018

Another approach.
Extract the logic of creating store from createApp to entry-script.js and entry-server.js. Pass store to createApp as an argument:

// app.js
export function createApp (store) {
  // create router instances
  const router = createRouter(store)
 ...
}

and in entry-server.js:

const store = createStore()
const { app, router } = createApp(store)

and in entry-client.js:

const store = createStore()
// prime the store with server-initialized state.
// the state is determined during SSR and inlined in the page markup.
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}
const { app, router } = createApp(store)

This way, store is replaced with the state from ssr before creating the app.

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

5 participants