React Flux: How to trigger a one-time initial load after authenticating - authentication

I'm using a higher-order wrapper component as a root that accomplishes 2 things:
Verify auth (redirect to login or home)
Do the initial load to store once the auth is complete.
I'm finding it hard to do those 2 things in this one wrapper class because I can't find a way to do a one-time initial load trigger if the user is not authenticated(has no existing session)
So for example I trigger a load when there is a session with a callback:
componentWillMount: function() {
LoginStore.addChangeListener(this._onChange);
var authData = AuthAPIUtils.checkForSession();
if(authData !== null) {
WebAPIUtils.loadStores(this.onBootstrapComplete);
}
},
"this.onBootstrapComplete" is a callback that will change the wrapper state
onBootstrapComplete: function() {
console.log("5-the final callback was made - onBootstrapComplete");
//localStorage.setItem( 'gvLoggedIn', true ); This is set true in home
this.setState({
bootstrapComplete: true,
});
},
"this.state.bootstrapComplete" is passed down the child components to switch from a loading spinner to rendering the components
render: function(){
if(this.state.loggedIn) {
var childrenWithProps = React.Children.map(this.props.children,function(child) {
return React.cloneElement(child,{bootstrapComplete : this.state.bootstrapComplete})
},this);
return (
<div className="wrapper-container">
{childrenWithProps}
</div>
)
}
else {
return (
<div className="wrapper-container">
<Login />
</div>
)
}
But when there isn't a session this callback solution for a one-time trigger breaks down.
I looked hard for a solution and the best I've come up with is:
The wrapper can only listen to a "LoginStore" which should only trigger once when there is a login and logout and then use _onChange to check for a log in and trigger the loading then.
Create a handler in the wrapper class and pass it down to the Login class as a callback.
Maybe one of those solutions is just fine(let me know if so) but I wanted to make sure I'm not doing something fundamentally poor to bootstrap my app.

For me it looks like the good approach. Just one point maybe, AuthAPIUtils should be an action file (but maybe it is already) and manage the dispatching. Reading at your code I think you're already using it this way. Otherwise I think your approach
1.The wrapper can only listen to a "LoginStore" which should only trigger once when there is a login and logout and then use _onChange to check for a log in and trigger the loading then.
2.Create a handler in the wrapper class and pass it down to the Login class as a callback.
is good

Related

Trying to get vue.js to render something conditionally based on a method in created()

I have a call in my created method which has an await.
I want to know that the results of that call are loaded so that i can conditionally show/hide things in the DOM.
Right now it looks like the DOM is being rendered before that method has completed. But I though that methods in created were called before the DOM rendered?
You're correct in assuming that the created hook runs before the component mounts. However, the lifecycle hooks are not waiting for async calls to complete. If you want to wait for that call to be completed and data to load, you can do so by using a Boolean that you set to true when your data has loaded.
Your template:
<div v-if='dataLoaded'>Now you can see me.</div>
in your vue instace
export default {
data () {
return {
dataLoaded: false
}
},
created () {
loadMyData().then(data => {
// do awesome things with data
this.dataLoaded = true
})
}
}
This way you can keep your content hidden until that call has resolved. Take care with the context when you handle the ajax response. You will want to keep this as a reference to the original vue instance, so that you can set your data correctly. Arrow functions work well for that.

Where do I listen for login status in React Native login view?

I've written a login component that works great except I can't figure out the proper way to move to the login page when login is successful.
I know it will somehow involve:
if (this.props.user) {
this.props.navigation.navigate("Main");
// I'm using react-native-navigation. But any implementation of "now go to the home page" would go here.
}
I've tried putting that at the top of my render method, which works, but I do get an error/warning (error in the debug console, warning in the simulator) saying "Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state."
Makes sense. But I can't conceive of the actual proper place to put it.
PS I tried this but it didn't work at all (navigation doesn't occur)
shouldComponentUpdate() {
if (this.props.user) {
this.props.navigation.navigate("Main");
return false;
}
return true;
}
Your last method is almost correct
shouldComponentUpdate() {
if (this.props.user) {
this.props.navigation.navigate("Main");
return false;
}
return true;
}
However, you're using this.props, which are the old props before the update. The new props are passed as the first argument to the method. It's also recommended to use componentWillReceiveProps over shouldComponentUpdate in this particular case as shouldComponentUpdate was introduced to prevent the component from updating.
So, this would make something like this:
componentWillReceiveProps(newProps) {
if (newProps.user) {
this.props.navigation.navigate("Main");
}
}

vue2: can not find a proper way to initialize component data by ajax

I have a component whose data is initialized by ajax. I know vue.js has provide several lifecycle hooks: Lifecycle-Diagram. But for ajax to initialize the data, which hook(beforeCreate, create, mounted, etc) is the best place to do it:
hook_name: function() {
ajaxCall(function(data) {
me.data = data;
});
}
Currently, i do it in mounted, making it to re-render the component. But i think we should get the data before the first render. Can someone figure out the best way to do it?
If you want to initialize your component with data you receive from a request, created() would be the most appropriate hook to use but it is a request, it might not resolve by the end of created or even mounted() (when even your DOM is ready to show content!).
So do have your component initialized with empty data like:
data () {
return {
listOfItems: [],
someKindOfConfig: {},
orSomeSpecialValue: null
}
}
and assign the actual values when you receive them in your created hook as these empty data properties would be available at that point of time, like:
created () {
someAPICall()
.then(data => {
this.listOfItems = data.listOfItems
})
/**
* Notice the use of arrow functions, without those [this] would
* not have the context of the component.
*/
}
It seems like you aren't using (or aren't planning to use) vuex but I'd highly recommend you to use it for for managing your data in stores. If you use vuex you can have actions which can make these api calls and by using simple getters in your component you would have access to the values returned by the request.

Best practice to change the route (VueRouter) after a mutation (Vuex)

I've searched a lot, but there is no clear answer to that. Basically, what should be the best practice to automatically change a route after a mutation?
Ex: I click a button to login() -> action login that makes an http call -> mutation LOGIN_SUCCESSFUL -> I want to redirect the user to the main page $router.go()
Should I wrap the action in a Promise, and then listen to the result to call the route change from the component?
Should I do it directly from the $store?
Does vuex-router-sync helps in any way?
Thanks a lot!
The answer to this questions seems to be somewhat unclear in the Vue community.
Most people (including me) would say that the store mutation should not have any effects besides actually mutating the store. Hence, doing the route change directly in the $store should be avoided.
I have very much enjoyed going with your first suggestion: Wrapping the action in a promise, and changing the route from withing your component as soon as the promise resolves.
A third solution is to use watch in your component, in order to change the route as soon as your LOGGED_IN_USER state (or whatever you call it) has changed. While this approach allows you to keep your actions and mutations 100% clean, I found it to become messy very, very quickly.
As a result, I would suggest going the promise route.
Put an event listener on your app.vue file then emit en event by your mutation function. But I suggest you wrapping the action in a promise is good way
App.vue:
import EventBus from './eventBus';
methods: {
redirectURL(path) {
this.$router.go(path)}
},
created() {
EventBus.$on('redirect', this.redirectURL)
}
mutation:
import EventBus from './eventBus';
LOGIN_SUCCESSFUL() {
state.blabla = "blabla";
EventBus.$emit('redirect', '/dashboard')
}
As of now (mid 2018) API of Vuex supports subscriptions. Using them it is possible to be notified when a mutation is changing your store and to adjust the router on demand.
The following example is an excerpt placed in created() life-cycle hook of a Vue component. It is subscribing to mutations of store waiting for the first match of desired criteria to cancel subscriptions and adjust route.
{
...
created: function() {
const unsubscribe = this.$store.subscribe( ( mutation, state ) => {
if ( mutation.type === "name-of-your-mutation" && state.yourInfo === desiredValue ) {
unsubscribe();
this.$router.push( { name: "name-of-your-new-route" } );
}
} );
},
...
}

In Vue.js, how do you prevent navigation for a subroute?

The nice thing about beforeRouteLeave is that you can prevent navigating away under certain conditions.
I have a setup that uses a subroute to render part of the page. I would like a navigation guard on the subroute to prevent switching to another one if the data is not saved.
{
path: '/customers/view',
component: ViewCustomerShell,
children: [
{path: ':id', name: 'ViewCustomer', component: ViewCustomer}
]
},
So when I visit /customers/view/12 and make a change, if they try to load /customers/view/13, I want to pop up the usual confirmation and potentially stop navigation. Since beforeRouteLeave is not called in this situation, what is the recommended approach for preventing navigation? It seems that watching $route would be too late, because then the navigation has already occurred.
Note: As mentioned above, beforeRouteLeave is not called in this situation; it doesn't work.
Note: Using onbeforeunload doesn't work because it only triggers when the entire page changes.
I have also posted the same answer here.
Dynamic route matching is specifically designed to make different paths or URLs map to the same route or component. Therefor, changing the argument does not technically count as leaving (or changing) the route, therefor beforeRouteLeave rightly does not get fired.
However, I suggest that one can make the component corresponding to the route responsible for detecting changes in the argument. Basically, whenever the argument changes, record the change then reverse it (hopefully reversal will be fast enough that it gets unnoticed by the user), then ask for confirmation. If user confirms the change, then use your record to "unreverse" the change, but if the user does not confirm, then keep things as they are (do not reverse the reverse).
I have not tested this personally and therefor I do not gurantee it to work, but hopefully it would have cleared up any confusion as to which part of the app is responsible for checking what change.
I know that this post is very old. but it was the first one I found when looking for the same problem.
I have no idea if there is a better solution nowadays but for those who are looking for a solution, I can share mine:
1. Define a global state
let isRouteChangeBlocked: boolean = false;
export function blockRouteChange(set?: boolean): boolean {
if (arguments.length == 1) {
isRouteChangeBlocked = !!set;
return isRouteChangeBlocked;
}
return isRouteChangeBlocked;
}
2. Replace the route function
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function(location: RawLocation) {
if (blockRouteChange()) {
if (confirm("Du hast ungespeicherte Änderungen, möchtest du fortfahren?")) {
blockRouteChange(false);
return originalPush.call(this, location) as any;
}
return;
}
return originalPush.call(this, location) as any;
};
3. Set the state
#Watch("note.text")
private noteTextChanged() {
blockRouteChange(true);
}
This does exactly what I want. If nowadays there is a better solution, let me know. You can get the full runnable example here: https://github.com/gabbersepp/dev.to-posts/tree/master/blog-posts/vuejs-avoid-routes/code/example
You could use a $route object inside your component to watch if it changes and then raise up the confirmation modal... This will get called whenever your route changes!
const Baz = {
data () {
return { saved: false }
},
template: `
<div>
<p>baz ({{ saved ? 'saved' : 'not saved' }})<p>
<button #click="saved = true">save</button>
</div>
`,
watch: {
'$route': function () {
if (this.saved || window.confirm('Not saved, are you sure you want to navigate away?')) {
// do something ...
}
}
}