Please, can anybody tell me why this function is running in an infinite loop?
<a:href="authenticationChoice(currentView['name'])" > **(Here. Keeps executing)**
<img
class="w-12 inline-block align-middle"
:src="showAuthenticationIcon(currentView['gitType'])"
/>
</a>
This is the direct function being call to dispatch the vuex action.
authenticationChoice(recieved) {
this.$store.state.gitSourceAuthenticationChoice = recieved;
this.$store.dispatch("gitSourceAuthenticationURL").then((response) => {
this.navigationURL = response["oauth2_redirect"];
console.log(this.navigationURL)
});
return this.navigationURL;
},
This is the action function in the vuex file
async gitSourceAuthenticationURL({ state }) {
return await axios
.get(`${Config.ApiUrl}/auth/login/${state.gitSourceAuthenticationChoice}`)
.then(response => {
return response.data
}).catch((error) => {
//console.log(error.data)
});
},
It is a property binding, so property bound to a value in VueJS is reactive, so every change detection, it ran and execute that.
That is why your method is getting called everything, when change detection is happening in VueJS.
<a:href="authenticationChoice(currentView['name'])" >
Kindly use click event or button to avoid this.
<button #click="authenticationChoice(currentView['name'])" >Text</button>
Or you can bind the click event on <a> tag, but you need to use <a> as button, that's not very recommendable.
Related
TL;DR: My UI freezes for .5-1s when I try to render a component that does a API fetch within a useEffect().
I have ComponentX which is a component that fetches data from an API in a useEffect() via a redux dispatch. I'm using RTK to build my redux store.
function ComponentX() {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchListData()); // fetch list data is a redux thunk.
}, [dispatch]);
...
return <FlatList data={data} /> // pseudo code
}
as you can see the fetch will happen everytime the component is rendered.
Now I have ComponentX in App along with another component called ComponentY.
Here's a rudamentary implementation on how my app determines which component to show. Pretend each component has a button that executes the onClick
function App() {
const [componentToRender, setComponentToRender] = useState("x");
if (componentToRender === "x") {
return <ComponentX onClick={() => setComponentToRender("y")}/>
} else {
return <ComponentY onClick={() => setComponentToRender("x")}/>
}
}
Now the issue happens when I try to move from ComponentY to ComponentX. When I click the "back" button on ComponentY the UI will freeze for .5-1s then show ComponentX. Removing the dispatch(fetchListData()); from the useEffect fixes the issue but obviously I can't do that since I need the data from the API.
Another fascinating thing is that I tried wrapping the dispatch in an if statement assuming that it would prevent a data fetch thus resolving the "lag" when shouldReload is false. The UI still froze before rendering ComponentX.
useEffect(() => {
if (shouldReload) { // assume this is false
console.log("reloading");
dispatch(fetchListData());
}
}, [dispatch, shouldReload]);
Any idea what's going on here?
EDIT:
I've done a little more pruning of code trying to simplify things. What I found that removing redux from the equation fixes the issue. By simply doing below, the lag disappears. This leads me to believe it has something to do with Redux/RTK.
const [listData, setListData] = useState([]);
useEffect(() => {
getListData().then(setListData)
}, []);
Sometimes running the code after interactions/animations completed solves the issue.
useEffect(() => {
InteractionManager.runAfterInteractions(() => {
dispatch(fetchListData());
});
}, [dispatch]);
I have a stenciljs component that has a nested stenciljs component:
<c-button-group>
<c-button></c-button>
</c-button-group>
In c-button-group template, I'm not using a slot and instead I'm using #Element() private element: HTMLElement to get all the nested elements after I render them in a loop.
#Component({
tag: 'c-button-group'
})
export class CButtonGroup {
#Element() private element: HTMLElement
render() {
return (
Array.from(this.element.children)
.map((child) => {
const el = child as ButtonInterface
return <c-button variant="grouped">{el.textContent}</c-button>
})
)
}
}
The reason why i use loop because i have to add attribute variant="grouped" for each nested element and i want to add it here, in the template. So this works but i noticed that if i assign a click event handler it doesn't work.
<body>
<c-button-group>
<c-button id="cBtn" #click="btnClickHandler">Years</c-button>
</c-button-group>
<script>
document.querySelector('#cBtn').addEventListener('click', () => {
console.log('Years')
})
</script>
</body>
Click event above doesn't work.
And seems this is obvious because in the render function i create a new 'c-button' based on a nested c-button.
My question is how do I pass all events that were assigned from the nested component to the new component that was created in the render function?
PS:
I noticed that if i use on-click attribute without a value to new c-button element, click event works:
Array.from(this.element.children)
.map((child) => {
const el = child as ButtonInterface
return <c-button
variant="grouped"
on-click
>{el.textContent}</c-button>
})
I just added on-click and it started working but in console i started getting errors:
TypeError: Failed to execute 'addEventListener' on 'EventTarget': parameter 2 is not of type 'Object'.
So at first this is not an option because i am getting that error and at second what if i need not only 'click' event, maybe there will be 5 or 10 events, so in that case i will have to add them all manually, not very comfortable to say the least.
Thank you so much in advance!
You can try using outerHTML and innerHTML
render() {
return (
Array.from(this.element.children)
.map((child) => {
child.setAttribute('variant', 'grouped')
return <div innerHTML={child.outerHTML}></div>
})
)
}
However this will only be able to pass events which are attached by attribute instead of addEventListener, as outerHTML is a string
<c-button-group>
<c-button id="cBtn" onclick="btnClickHandler()">Years</c-button>
</c-button-group>
Here btnClickHandler will work for rendered children elements
So the only way i found myself is to use <slot /> and #Element() private element: HTMLElement together.
#Element() private element: HTMLElement
componentWillLoad() {
Array.from(this.element.children).forEach(el => {
el.setAttribute('variant', 'grouped')
})
}
render() {
return <slot />
}
Using <slot /> allows as to use all events and using this.element.children gives the ability to edit DOM elements before their rendering, we can remove, add any attribute, class, and so on...
I am trying to pass a variable from a Parent (page) component to a Child (modal) component. After reading a few examples, this works fine. The variable in question is brought in from another component as a route param. If i refresh the page, the variable is lost and can no longer be passed to the child. My question is, is the best way to persist this using the store, or is it possible to persist another way if the user refreshed? Any help would be appreciated
Parent
<b-container>
<Modal v-show="displayModal" #close="closeModal">
<template v-slot:body>
<ExpressionCreate v-show="displayModal" :lender-id="lenderId" #close="closeModal"/>
</template>
</Modal>
<div class="card" style="width: 100%;">
<div class="card-header">
<h5>{{this.lenderName}}</h5>
<b-alert :show="this.loading" variant="info">Loading...</b-alert>
</div>
<b-btn block variant="success" #click="showCreateLenderModal">Add Expression</b-btn>
....
created () {
this.lenderId = this.$route.params.lenderId
...
navigate () {
router.go(-1)
},
showCreateLenderModal () {
this.displayModal = true
},
toggleDisplayModal (isShow) {
this.displayModal = isShow
},
async closeModal () {
this.displayModal = false
}
Child
<label>lender id:</label>{{this.lenderId}}
...
props: {
lenderId: {
type: Number
}
},
You can use VueSession to persist.
Simply persist your lender_id with
this.$session.set('lender_id', this.lender_id)
and you can get it later as
saved_lender = this.$session.get('lender_id')
if (saved_lender) {
// handle saved case here
}
You can use query params in URI by doing:
$route.push('/', { query: { param: 3123 } })
This will generate the URL /?param=3123 and this will work after refresh. Or, you can use vuex and vuex-localstorage plugin, that let you persist data in the browser, and you can close the browser and open again and data will be restored.
In order for the state of application to persist, I recommend that you use vuex to manage the state and use a library that abstracts the persisting to vue in a clean way. One popular library is vuex-persist. https://www.npmjs.com/package/vuex-persist
If you dont want to use a store, VueSession, a package linked here should do the trick
I have an app that has a Parent component (app.js) and 2 child sibling components ( Modal.vue and Cart.vue).
I am getting a response back from a call within Modal.vue that needs to trigger a function within Cart.vue right after. What are the best options of going about this?
I don't currently use Vuex, which I know would make this easier.
You can use refs like so:
<parent>
<modal #triggerFunc="triggerFunc"/>
<cart ref="$cartRef"/>
</parent>
...
triggerFunc(){
this.$refs.$cartRef.functionIwantToTrigger()
}
You could create an event bus:
https://www.digitalocean.com/community/tutorials/vuejs-global-event-bus
You can use Custom Events for this using the root Vue instance of the current component tree.
In Modal.vue:
Where you are getting a response back from a call, simply emit a custom event like:
this.$root.$emit('onupdate', data);
In Cart.vue:
Simply listen to this root emitted event like
var vm = new Vue({
data: {},
mounted() {
this.$root.$on('onupdate', data => {
// trigger a function within Cart.vue
this.myFunction(data)
});
}
methods: {
myFunction: function(data) {
console.log(data);
}
}
})
I have written the following code in a single Vue component. I am very new to Vue. I want to know how can I update the msg variable in the following and pass the updated value to the template in this Vue component:
<template>
<div class="dropzone">
<form id="dropzone" method='POST' enctype="multipart/form-data" v-on:drop="upload" #dragover.prevent>
<my-vue-component v-model="msg" ref="markdownEditor" />
</form>
</div>
</template>
<script>
export default {
data: () => {
return {
msg: 'zyx'
}
},
methods: {
upload: (e) => {
self.msg = '123'
}
}
}
</script>
I am really stuck and none of the links that google redirects me to are out of my reach. Because all of them either talk about new Vue({}) or dont provide much on export default.
Am I doing something wrong here?
I have also tried this.msg, but then I get the error saying msg of undefined...
Change this:
upload: (e) => {
self.msg = '123'
}
to this:
upload (e) {
this.msg = '123'
}
Note that upload (e) { is short-hand for upload: function (e) {.
The key changes are:
Use this instead of self.
Don't use an arrow function because that binds this to the surrounding scope and in this case that's the global scope. What you want is for this to be bound to your component instance (which is actually a Vue instance) so you need to use a normal function. Vue tries to ensure the binding is correct but it can't do that with an arrow function because it is already bound.
The config options that appear in a component like this are almost all the same as the config options that you would pass to new Vue, so if you see examples that are using the other one it will rarely make any difference. Usually it is pretty obvious when a config setting doesn't make sense for one or the other.