Vue Router params not updating in page - javascript

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>

Related

Why isn't this React component updated when dependency changes?

I have this simple React component with a search bar, it gets the input value and navigates to an URL setting the input value as a query param. Once in the component, it gets the 'keyword' from the URL query params and calls an API to get results.
It only works the first time. But if I change the input value it doesn't update the 'keyword' variable value, even though the URL is properly updated.
Why isn't my variable updated?
export default function Resultados () {
let keyword = new URLSearchParams(window.location.search).get('keyword')
const apiKey = '123' ;
useEffect(()=>{
axios
.get(`https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&language=es-ES&query=${keyword}&page=1&include_adult=false
`)
.then(res=>{
console.log(res.data)
})
},[keyword])
return (
<>
<h2>Resultados de {keyword}</h2>
</>
)
}
I've included the keyword variable on the useEffect dependency array, but it seems the variable is not changing.
The dependency array to a useEffect will never cause your component to render. In fact, it's only useful if the component is already rendering, at which point it can skip the effect if nothing has changed. Instead, if you want the component to render, you must set state (either here or in some parent component).
So you either need to write custom code which detects when the url changes, and at that point set state; or you can use an existing routing library such as react-router or react-location, which have already written that code for you. For example, here's how you would do it in react-router:
import { useSearchParams } from 'react-router';
export default function Resultados () {
const [searchParams] = useSearchParams();
const keyword = searchParams.get('keyword');
// rest of your code is the same
}

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.js vuex props don't rerender

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);

MobX store doesn't get updated in react

What i want to achieve
I have a store object and i want to populate one of it's properties (which is an empty array) with instances of another object. And i want one of my react component automatically updated when part of the mentioned array instance is changed.
What my problem is
By logging out the value of this constructed store object i can see the change in the browser console, but it doesn't get updated automatically in the react component when its value changed.
So i'd like to get hints, examples of how to implement a solution like this in general.
Details
My project
I want to create a MobX store called Session which would store everything my react webapp needs.
This webapp would be a document handling tool, so after creating new or loading existing documents i want to add the Document object to the Session (into an object array called documents).
Some more details: a Document consists of one or more section. So using a WYSIWYG editor i add its content to the given section every time it's content changes.
Problem
I can add a new Document to the Session, i can update section as well(I can log out the value of a section in console), but using the Session reference to this document and section in a react component it doesnt update its state when section value is changed.
To my understanding in my example the reference of a Document is not changed when the value of a section is changed and hence it doesn't trigger MobX to react.
What i found so far
I started to dig in the deep, dark web and found this article.
So i started getting excited since asStructure (or asMap) seemed to solve this issue, but it looks like asStructure in MobX is deprecated.
Then i found this issue thread, where a thing called observable.structurallyCompare is mentioned. But again i found nothing about this in MobX documentation so im puzzled how to implement it.
So im stuck again and have no idea how to solve this problem.
Code excerpts from my project
This is how i reference to the mentioned Session value in the main react component:
import userSession from '../client/Session';
import {observer} from 'mobx-react';
#observer class App extends React.Component {
...
render() {
return (
...
<div>{JSON.stringify(userSession.documents[0].content.sections)}</div>
...
This is how i update the section in the editor react component:
import userSession from '../../client/Session';
...
handleChange(value,arg2,arg3,arg4) {
this.setState({ content: value, delta: arg4.getHTML()});
userSession.documents[0].updateSectionContent(this.props.id, this.state.delta);
}
}
...
Session.js excerpt:
class Session {
constructor(){
extendObservable(this, {
user: {
},
layout: {
},
documents: []
})
//current hack to add an empty Document to Session
this.documents.push(new Document());
}
//yadayadayada...
#action addNewSection() {
userSession.documents[0].content.sections.push({
type: "editor",
id: userSession.documents[0].getNextSectionID(),
editable: true,
content: "",
placeholder: "type here to add new section..."
});
}
}
var userSession = window.userSession = new Session();
export default userSession;
Document.js
import {extendObservable, computed, action} from "mobx";
import userSession from "./Session";
class Document {
constructor(doc = null) {
if (doc == null) {
console.log("doc - no init value provided");
extendObservable(this,{
labels: {
title: "title",
description: "description",
owners: ["guest"]
},
content: {
sections: [
{
type: "editor",
id: "sec1",
editable: true,
placeholder: "this is where the magic happens"
},
]
}
})
} else {
console.log("doc - init values provided: ");
extendObservable(this,{
labels: doc.labels,
content: doc.content
});
}
}
getNextSectionID(){
return `sec${this.content.sections.length}`;
}
#action updateSectionContent(sectionID, delta) {
console.log("doc - looking for section " + sectionID + " to update with this: " + delta);
let index = this.content.sections.findIndex(
section => section.id === sectionID
);
userSession.documents[0].content.sections[index].content = delta;
}
}
export default Document;
Ps.: atm moment i don't remember why i made Document properties observable, but for some reason it was necessary.
Unfortunately, you are implementing mobx with React the incorrect way. To make it more comprehensible, I suggest you look into the implementation of the HOC observer that mobx-react provide.
Basically, what this HOC does is to wrap your component inside another React component that implement shouldComponentUpdate that check when the props referred inside render function change, and trigger the re-render. To make React component reactive to change in mobx store, you need to pass the store data to them as props, either in React traditional way, or via the inject HOC that mobx-react provide.
The problem while your code does not react to change in the store is because you import the store and use them directly inside your component, mobx-react cannot detect change that way.
To make it reactive, instead of import it and use it directly in your App component, you can pass the store as a prop as follow:
#observer
class App extends React.Component {
...
render() {
return (
...
<div>{this.props.sections}</div>
...);
}
}
Then pass the store to App when it's used:
<App sections={userSession.documents[0].content.sections} />
You can also have a look at how to use inject here: https://github.com/mobxjs/mobx-react.
An just a suggestion: before jumping directly on some best pattern, try to stick with the basic, standard way that library author recommend and understand how it works first, you can consider other option after you got the core idea.

How to destroy a VueJS component that is being cached by <keep-alive>

I have a Vue component that's kept alive using Vue's element for caching purposes. However, the problem I am having right now is that once I sign out of one account and create a new account on my Vue application, the component I'm "keeping alive" is being reflected for the new user (which obviously isn't relevant for the new user).
As a result, I want to destroy that component once the user signs out. What is the best way to go about this?
I've managed to solve my issue in the following way. Essentially, if the user is logged in, keep the dashboard alive. Else, don't keep the dashboard alive. I check if the user is logged in or out every time the route changes by "watching" the route (see below). If you are reading this and have a more elegant solution - I'd love to hear it.
The following is the code for my root component
<template>
<div id="app">
<!-- if user is logged in, keep dashboard alive -->
<keep-alive
v-bind:include="[ 'dashboard' ]"
v-if="isLoggedIn">
<router-view></router-view>
</keep-alive>
<!-- otherwise don't keep anything alive -->
<router-view v-else></router-view>
</div>
</template>
<script>
import firebase from "firebase";
export default {
name: 'app',
data() {
return {
isLoggedIn: false // determines if dashboard is kept alive or not
}
},
watch: {
$route (to, from){ // if the route changes...
if (firebase.auth().currentUser) { // firebase returns null if user logged out
this.isLoggedIn = true;
} else {
this.isLoggedIn = false;
}
}
}
}
</script>
I had the same problem and I solved it by using an array of cached components and bus event.
Here is my HTML keep-alive App.vue:
<keep-alive :include="cachedComponents">
<router-view></router-view>
</keep-alive>
Here is what I'm doing in the created() life cycle:
created() {
// Push Home component in cached component array if it doesn't exist in the array
if (!this.cachedComponents.includes('Home')) {
this.cachedComponents.push('Home')
}
// Event to remove the components from the cache
bus.$on('clearCachedComponents', (data) => {
// If the received component exist
if (this.cachedComponents.includes(data)) {
// Get the index of the component in the array
const index = this.cachedComponents.indexOf(data)
// Remove it from the array
this.cachedComponents.splice(index, 1)
}
})
}
And inside another component just trigger the event and send the component to remove in parameter.
Another.vue
bus.$emit('clearCachedComponents', 'Home')
If you don't know how to make a bus event there are lot of tutorials on the internet like this to do that. But bus event is my way to do that and you can use everything you want like a child emitter or Vuex. That I want to show is to use an array of components to manage your cache. All you have to do is to add or remove your components in the array.
for anyone looking for a solution that destroys the cache
in my case I was using this in a logout route, replace router.app with this.$root in Vue instances and the $children index/nesting may differ for your app
setTimeout(() => {
var d = [];
for(var vm of router.app.$children[0].$children) {
if(vm._inactive === true)
d.push(vm);
}
for(var vm of d) {
vm.$destroy();
}
});
If your problem is that the component is still holding the old user's data, the only option is resetting it with an internal reset function, which reloads the data for the new user one way or another.
See:
http://jsfiddle.net/paolomioni/hayskdy8/
var Home = Vue.component('Home', {
template: `<div><h1>{{ title }}</h1>
<input type="button" value="click to change text" v-on:click="title = Math.random()"">
<input type="button" value="click to reset component" v-on:click="reset"></div>`,
data: () => {
return {
title: 'BBB'
}
},
methods: {
reset() {
this.title = 'BBB'
}
}
});
In the fiddle, click on the button "change text" to change the text: if you click the checkbox twice to switch view and back again, you will see that the number you've generated is still kept in memory. If you click on the "reset" button, it will be reset to its initial state. You need to implement the reset method on your component and call it programmaticaly when the user logs out or when the new user logs in.

Categories

Resources