Vuejs components to wait for initialisation - javascript

To give some context, I've a vuejs application using vue-router and vuex.
app.vue Bootstrap code
foo.vue Some component that renders on route /foo
bar.vue Some component that renders on route /bar
When the application starts I need to sync the state reading from device storage. Since this is an async operation it's wrapped on a vue-action and called on app.vue.
When entering the /foo route by the time the component is mounted the $store is not yet update from the device storage
How can all components ensure the initialisation is made before they are mounted?

What I do in these situations is use a v-if on your root <router-view> element to render only when the data you expect to be there has populated, e.g.
<script>
export default {
computed: {
hasLoadedData() {
// Or whatever criteria you decide on to represent that the
// app state has finished loading.
return this.$store.state.something !== null;
}
}
}
</script>
<template>
<div id="app">
<router-view v-if="hasLoadedData" />
<p v-else>Loading...</p>
</div>
</template>

Related

Vue3 LocalStorage set after component render

I have a nav bar that loads user data, all of this happens after a user successfully logs into the application. The problem is, localStorage must be setting slightly after I load the nav bar. If I wrap it in a setTimeout() everything works but I would rather my variables be reactive in nature since they can change based on user activity.
Toolbar.vue
<template>
<!--begin::Toolbar wrapper-->
<div class="d-flex align-items-stretch flex-shrink-0">
<h2>check for value</h2>
<div v-if="activeAccountId">{{activeAccountId}}</div>
</div>
<!--end::Toolbar wrapper-->
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "topbar",
data() {
let activeAccountId = ref(JSON.parse(localStorage.getItem('activeAccountId') || '{}')).value;
return {
activeAccountId
}
}
});
</script>
I've tried using watchers, and using setup() verses data(), but nothing seems to work properly. As I mentioned, setTimeout() does work but I'd rather avoid manually triggering a timeout and let vue handle things how it wants to.
Here's a simple example, I can't setup a dummy code side since it won't have the localStorage item set.
For some additional context, after the user is logged in, I am hitting the API with an async() to get the account information and storing the account data in localStorage. I'm guessing at the same time the router is trying to load the navbar area which is why the localStorage items aren't available when the component mounts.
I don't know the vue3 words to use, but ideally I would want some type of async/await call to localStorage because the ref() doesns't seem to be working how I thought it would. It's as if the ref() doesn't see localStorage get updated.
localStorage being synchronous is the main issue.
use mounted lifecycle hook. and initialize user information there
Vue calls the mounted() hook when the component is added to the DOM. You can try putting your initial code in the mounted method and also try to change your code like this
export default defineComponent({
name: "topbar",
data() {
return {
activeAccountId:""
}
},
mounted(){
this.activeAccountId = JSON.parse(localStorage.getItem('activeAccountId')|| '{}');
}
});

Vue - How to pass data to child component after route is entered

Hey I'm relatively new to vue.js.
I have a Home.vue which creates a data array after successful login and entering the route.
I tried it with
<template>
<div class="home">
***
<History :data="this.cData" />
***
</div>
</template>
***
beforeRouteEnter(to, from, next) {
next(async (vm) => {
vm.cData = await vm.select(vm.handle);
console.log(vm.cData);
});
},
But the data is send before beforeRouteEnter() and the History component needs it for creating itself.
Is there a way do to it?
If the History component needs it for creating itself, then you should defer creation of the component (with v-if) until cData is ready. cData should be declared upfront in the data object with value null.
You're also doing this.cData in the template but it should be just cData.
<History v-if="cData" :data="cData" />

Vue - Call a method from a different component (uses vue-routes)

Here is my base App.vue that holds the router-view:
<template>
<div>
<Navbar/>
<div class="container-fluid">
<router-view></router-view>
</div>
</div>
</template>
<script>
import Navbar from './components/Navbar.vue';
export default {
name: 'App',
components: {
Navbar
}
}
</script>
In the Navbar.vue, I have a method:
methods: {
getLoggedStatus(){
console.log('asdasd')
}
}
Lastly, I have a Login.vue that is loaded there on the router-view. I wanna acess the getLoggedStatus() from the Navbar.vue within Login.vue. How can I achieve this?
I tried putting a ref to the Navbar tag in App.vue:
<Navbar ref="navvy"/>
And calling in on the Login.vue with:
this.$refs.navvy.getLoggedStatus()
But it doesn't work.
A ref only works on children. You're rendering <Navbar> within app, so you cannot call that ref from login. You can only access this.$refs.navvy from App.vue.
There are several solutions for your problem.
Emit an event from Login to App, so App calls the method from a ref.
You can set a listener in the router-view, as:
<router-view #loggedStatus="callLoggedStatus" />
In your login, when you would want to call the navbar getLoggedStatus, you would instead emit that event:
this.$emit('loggedStatus')
And then in App.vue, you would defined a callLoggedStatus methods that call the ref:
callLoggedStatus() {
this.$refs.navvy.getLoggedStatus();
}
Given that you add the ref to the <Navbar> component in the APP template.
This solution is arguably the most similar to your proposed code, but I think it is a mess and you should avoid it, since you can end up listening to a lot of different events in your App.vue.
Use Vuex
I don't exactly know what getLoggedStatus does, but if you want to change how your navbar behaves when the user is logged in, you should probably setup a vuex store, so you register there wether the user is logged or not. Then in your navbar component you render things conditionally depending upon the user is logged or not.
#Lana's answer follows this idea, and is probably the closest to the official way to thins in Vue.
 Use an event emitter
If you want to directly communicate between components that are not in the same family, I think an event emitter is a reasonable choice. You could setup an application wide event emitter after creating the app:
const app = new Vue({...});
window.emitter = new Vue();
(in the example we use a new Vue as event emitter. There is also the 'events' module which allow to use a EventEmitter)
And then any component can use it to send messages, so Login could do:
window.emitter.$emit('user-logged', myCustomPayload);
And Navbar on the other hand could do:
window.emitter.$on('user-logged', function() {
this.getLoggedStatus();
})
This last option is not well considered in the Vue community -Vuex is preferred- but for small applications I think it is the simplest.
The dirty hack
You can always export your component to window. In the Navbar created hook you could do:
created() {
window.Navbar = this;
}
And then in Login.vue you could, at any time:
window.Navbar.getLoggedStatus()
And it will work. However, this is surely an anti pattern and can cause a lot of harm to your project maintainability if you start doing this with several components.
It looks like getLoggedStatus returns some global state of the app. Use Vuex for managing these global variables.
Create a store like this:
// Make sure to call Vue.use(Vuex) first if using a module system
const store = new Vuex.Store({
state: {
loggedStatus: 0
},
getters: {
getLoggedStatus: state => {
// to compute derived state based on store state
return state.loggedStatus
}
}
})
Use store.state.loggedStatus in any Vue-component to access the global state or use getters like store.getters.getLoggedStatus if you need to compute derived state based on store state.

Vue.js component within component, manipulate parent data

ive seen afew answers that sort of answer my question but not fully, so let me explain what I want to do.
We use a global #app div within the layout of our website, which is a Laravel project. So all pages will be the same main Vue instance, due to this i'm separating key functionality into components.
So, the first example is just a simple Tab component, this either separates any children into tabs, or accepts some data which the single child component then renders.
So below i'm injecting some data from another component, this ajax component literally just does an ajax call, and makes the data available within it's slot.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
#{{ row['example' }}
</div>
</div>
</tabs>
</div>
</ajax>
Now this is all well and good, to a point, but this falls down with the below code. This contains a component which will allow the used to drag and drop elements, it re-arranges them by literally moving the data around and letting Vue handle the DOM changes.
This will of course work fine within it's own data which you have injected in, but when you change the data within the component below this then clears this child component.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
<draggable :injected="row">
<div>
<div v-for="item">
#{{ item }}
</div>
</div>
</draggable>
</div>
</div>
</tabs>
</div>
</ajax>
I need to find a way to make any changes to this data apply to the parent data, rather than the data passed into the child components.
What is the best practice to do this!?
Edit 1
Basically, I need any child component's manipulate the data within the ajax component. The children within ajax could change, or there could be more, so I just need them all to do this without knowing what order or where they are.
It is hard to come up with specifics on this one, but I am going to try to put you in the right direction. There are three ways to share data between components.
1) Passing down data via props, emitting data up via custom events
The passing down of data via props is a one-way street between the parent and child components. Rerendering the parent component will also re-render the child and data will be reset to the original state. See VueJS: Change data within child component and update parent's data.
2) Using a global event-bus
Here you create an event bus and use this to emit the data to different components. All components can subscribe to updates from the event bus and update their local state accordingly. You initiate an event bus like this:
import Vue from 'vue';
export const EventBus = new Vue();
You send events like this:
import { EventBus } from './eventbus.js'
EventBus.$emit('myAwsomeEvent', payload)
And you subscribe to events like this:
import { EventBus } from './eventbus.js'
EventBus.$on('myAwsomeEvent', () => {
console.log('event received)
})
You still need to manage state in the components individually. This is a good start with an Event bus: https://alligator.io/vuejs/global-event-bus/
3) Using Vuex
Using Vuex extracts the component state into the Vuex store. Here you can store global state and mutate this state by committing mutations. You can even do this asynchonously by using actions. I think this is what you need, because your global state is external to any components you might use.
export const state = () => ({
resultOfAjaxCall: {}
})
export const mutations = {
updateAjax (state, payload) {
state.resultOfAjaxCall = payload
}
}
export const actions= {
callAjax ({commit}) {
const ajax = awaitAjax
commit('updateAjax', ajax)
}
}
Using vuex you keep your ajax results separated from your components structure. You can then populate your state with the ajax results and mutate the state from your individual components. This way, it doesn't matter whether you recall ajax, or destroy components since the state will always be there. I think this is what you need. More info on Vuex here: https://vuex.vuejs.org/

React Router changing params doesn't fire componentWillRecieveProps

The Problem: Changing the parameters of a <Route /> component does not update the component it is rendering. The route change is shown in the URL bar, but directly rendering {this.props.match.params.id} shows the old :id and not the new one reflected in the URL bar.
Update: I fixed this by moving the <BrowserRouter /> out from the index.js file and into the App.js file. It is no longer the direct child of Provider and is instead the child of the App component. No clue why this makes everything suddenly work.
What I am doing: I have a <Link to="/user/11" /> that goes from user/7 (or any current ID) to a /user/11
The componentWillReceiveProps(newProps) of the component it is rendering is not fired.(This component is connected using react-redux if that helps any. I tried applying withRouter around the connection and that did not help)
If I manually refresh the page in chrome (using CTRL-R or the refresh button) the page shows the new data, rendering the "new" param.
TLDR: Switching from /user/7 to /user/11 does not fire that componentWillRecieveProps function and therefore leaving the component displaying the old state
Question: What am I doing incorrectly here that causes componentWillReceiveProps to not fire.
I am using react-router v4 and the latest create-react-app
This is my CWRP function:
componentWillReceiveProps(newProps) {
console.log("getProps")
this.props.getUser(newProps.match.params.id)
if (newProps.match.params.id == newProps.currentUser.id) {
this.setState({ user: "currentUser" })
} else {
this.setState({ user: "selectedUser" })
}
}
This is the full code of my component: https://gist.github.com/Connorelsea/c5c14e7c54994292bef2852475fc6b43
I was following the solution here and it did not seem to work for me. Component does not remount when route parameters change
You'll need to use React Router Redux

Categories

Resources