Vue.js vuex props don't rerender - javascript

I'm using Vuex and display data like this:
<template>
<div>
<div>
<div v-for="port in ports">
<port :port="port"></port>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState({
ports: state => state.ports.ports,
})
}
}
</script>
The problem that I have now is that when I change the ports state in another component it updates the ports state correctly but the port in the v-for loop is not updated.
How do I make sure the data in the port component rerenders correctly as well?

Vue Beginner Gotchas: Why isn't the DOM updating?
Most of the time, when you change a Vue instance’s data, the view updates. But there are two edge cases:
When you are adding a new property that wasn’t present when the data was observed.
When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property.
If you are changing your ports data by doing something like this:
this.ports[0] = 1234;
Or something like this:
this.ports[4].property = 'some value';
Then Vue won't be able to detect that a change occurred. You can get around this pitfall by doing:
Vue.set(this.ports, 0, 1234);

Related

How to correctly pass properties of a nested Svelte Store to child components

i have a Svelte Store with a nested object inside.
(I don't want to flatten the store, since in my real use-case the order of the elements (arrays appear too) is very important.)
export const rootStore = writable({
test : {
a : 0
}
})
Now i want to pass the test property of this store to a child component, and I am questioning myself, what would be the best way to do this.
I did it like that:
Parent.svelte
<script>
import Child from "./Child.svelte"
import {rootStore} from "./store.js";
rootStore.subscribe((value) => console.log(value))
</script>
<Child attributes={$rootStore.test}/>
<p>
Value of test.a is {$rootStore.test.a}
</p>
Child.svelte
<script>
import {rootStore} from "./store.js";
export let attributes;
const triggerUpdate = () => {
$rootStore = $rootStore;
}
</script>
<input type="number" bind:value={attributes.a}/>
<button on:click={triggerUpdate}>
Explicitly trigger update
</button>
But this doesn't seem a good idea, since now i am working on a reference of item in my child component, that does not trigger other subscribers when I edit a.
The edit will only become visible when I explicitly update the rootStore object as done in triggerUpdate().
Here is the full REPL: https://svelte.dev/repl/57bce526799843b19838913cd598f201?version=3.50.1
Is it necessary to always work on the $rootStore in the child components?
Should I only pass some IDs as properties to the child component, that will not be changed anytime?
Any ideas are welcome.
You just have to bind the property to make it hook into updates:
<Child bind:attributes={$rootStore.test}/>
REPL

Component's data property is not reactive when fetched via fetch method

I have a simple "show" component that fetches some data via an API call and renders it on the page.
<template>
<div class="showNote">
<div>
{{note.title}}
</div>
</div>
</template>
<script>
import NotesAPI from '#/api/notes'
export default {
data() {
return {
note: {}
}
},
async fetch() {
let id = this.$route.params.id
let response = await NotesAPI.fetchNote(id)
let data = response.data
this.note.title = data.title
}
}
</script>
I'm finding that the rendered template is blank even though the data has been fetched and set on the data field. I can confirm this via the Vue Inspector
It seems like the data property is not reactive and I'd need some way to force the re-rendering of the page. But obviously this would be the wrong approach. What am I missing? Why is the component not rendering my data changes?
Try to use this.$set to set nested value of an object :
this.$set(this.note,'title', data.title)

Vue Router params not updating in page

Hello I have some conditional logic in my view based on the current page
in setup I have
const curVidType = ref(route.params.videoTopic);
I return it and then print out like
<h1>Welcome to {{ curVidType }} videos</h1>
However it only works if I refresh. If I browse to a new page it stays the old one even though the browser url changes. after refresh it updates. Thanks so much for any help
Try adding :key to your Component to make sure it updates when param changes:
<your-component :key="$route.params.videoTopic"></your-component>
You're initializing the variable as a const, which means it does it once and then doesn't set it. If you change curVidType to a computed value, you can have it react to changes in the router params.
computed: {
curVidType() {
return route.params.videoTopics
}
}
This will have the value of curVidType set to change when videoTopics does
EDIT:
Having read the comments and looked at the comments and read some of the documentations, ref() will create a reactive object but I'm not sure it's reacting to the original object. But it looks like the toRef() function can create that bridge.
const curVidType = toRef(route.params, 'videoTopics')
Should allow for curVidType.value to also reflect changes to to route.params.videoTopics
Would be nice if you could share some more code with us in order to help you.
This should just work fine
<template>
{{ curVidType }}
</template>
<script>
import { useRoute } from 'vue-router';
export default {
setup() {
const { params } = useRoute();
const curVidType = params.videoTopic
return {
curVidType,
};
},
};
</script>

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/

Categories

Resources