Don't show invalid UI state when data is loading - javascript

In my Vue 2.6.X app I have single-file components such as:
<template>
<div>
<div v-if="users.length">
<!-- render the users in a list -->
</div>
<div v-else>
Warning: no users were found
</div>
</div>
</template>
<script>
import userService from '#/services/user-service'
export default {
name: 'UserList',
async created () {
this.users = userService.loadUsersFromServer()
},
data () {
return {
users: []
}
}
}
</script>
A problem with this approach is that after the component renders but before the users are loaded, the warning message is briefly shown in the UI. Is there a recommended way to avoid this "flash of invalid UI state"?
One approach is to introduce a loading data prop that is initialised to true, then set to false when the AJAX request has completed, and change the template to
<template>
<div>
<div v-if="loading">
Users are loading, please wait....
</div>
<div v-else-if="users.length">
<!-- render the users in a list -->
</div>
<div v-else>
Warning: no users were found
</div>
</div>
</template>
In this simple example, this should work fine, but if we need to wait on multiple requests before showing the UI, it could become cumbersome, is there a better/simpler approach?

Use Promise.all() to wait on multiple promises before setting loading to false.
That is simplest and what I do most of the times
Or you can defer navigation until the data is loaded if you use router

Related

How to make lazy-load html tags in NuxtJs

I am new to Nuxtjs. I am making page called photos which shows a lot of DOM because the api I fetch photos doesn't provide pagination queries (page, limit). Currently it returns data containing 5000 photos.
For performance, I want to find a away to render only html tags that is visible in viewport. Other html tags should be lazy render until user scroll down. I have tried nuxt-lazy-load but this package only lazy load the image, it still render all html tags.
I would highly appreciate all advices. Thank you.
I use Nuxt 2.15.8, Vue 2.7.10.
<template>
<div>
<h1>Page Photos</h1>
<div v-if="photos.length > 0">
<div v-for="(photo, index) in photos" :key="photo.id"> **(I want to lazy render this element until user scroll down to here)**
<h5>{{ `${index + 1}. ${photo.title}` }}</h5>
<img
:alt="photo.title"
:src="photo.url"
width="600"
height="600"
/>
<br />
</div>
</div>
</div>
</template>
<script>
export default {
async asyncData(context) {
try {
const response = await fetch(context.$constants.photosApi).then((r) =>
r.json()
);
return {
photos: response || [],
};
} catch (error) {
console.log("error", error);
return {
photos: [],
};
}
},
};
</script>
vue-virtual-scroller is what you're looking for.
It will render in the DOM, only the elements visible in your current viewport (you can double check by inspecting the DOM and seeing that there is no more elements than the ones you see).
As for the lazy-load of images, here you go: https://stackoverflow.com/a/72232543/8816585
You can of course totally mix both techniques.

Unable to access data passed in through react router

I'm trying to pass in data that I received from my backend to another page on my frontend. I'm using react-router-dom and withRouter to pass my data from the product.js page to the result.js page on the frontend. I keep getting this error though:
Uncaught TypeError: Cannot read properties of undefined (reading 'state')
And the code always breaks on this line of code, it doesn't go past it (this code is present in the result.js page code down below):
<div className="score">hAPPi SCORE: <br/> <span>{this.props.location.state.data.data.score}</span></div>
(product.js PAGE) Here's the code of the piece in the react page that passes the received data from the backend to another page. What this code does is basically take in a user's input (through speech), send the transcript to the backend, and the backend will shoot out feedback, scores, etc..., and that data will then come back to this product.js page. From there, I want to pass that data to the result.js page.
class Product extends Component {
handleRedirect() {
// Redirect user to the results page
console.log(this.state.data)
console.log(this.state.data.data.feedback)
this.props.history.push("/results", {data: this.state.data}); // Sending backend data to frontend
}
}
export default withRouter(Product);
(result.js PAGE) Here's the code of the react page that is supposed to receive the data from the first page and display it on the page:
class ResultPage extends Component {
render() {
console.log(this.state.data) // trying to console log it, but it won't work
return (
<div className="Container">
<div className="result-section">
<div className="score">hAPPi SCORE: <br/> <span>{this.props.location.state.data.data.score}</span></div>
<div className="scale-container">
<Scale scale-id="scr-scale" style="score-scale" score={this.props.location.state.data.data.score}/>
</div>
</div>
<div className="analysis-section">
<div className="analysis-container">
<div className="analysis-title">
In-Depth Analysis
</div>
<div className="keywords-container">
<div className="section-titles">
Keywords
</div>
<div>
<ul>
{this.props.location.state.data.data.keywords.map(kw =>
<h3>{kw}</h3>
)}
</ul>
</div>
</div>
<div className="entity-container">
<div className="section-titles">
Entities
</div>
<div>
<ul>
{this.props.location.state.data.data.entities.map(en =>
<h3>{en}</h3>
)}
</ul>
</div>
</div>
<div>
<h1>Result</h1>
<div>
{this.props.location.state.data.data.feedback}
</div>
</div>
<hr></hr>
</div>
</div>
</div>
);
}
}
export default ResultPage;
I keep getting errors whenever I use the this.props.location.state.data.(something) but when I replace that with hard-coded numbers/strings, the page works fine. I'm not sure I'm doing anything wrong in terms of passing the data from the product.js page to the result.js page, but I'm pretty sure I'm doing something wrong in accessing that data from the result.js page using this.props.location....
Some help would be greatly appreciated!
You haven't shown where/how the ResultPage component is rendered. It can receive the location object as a prop in a couple ways.
Rendered directly by a Route component on one of the rendering props.
Example:
<Route path="/result" component={ResultPage} />
<Route
path="/result"
render={routeProps => <ResultPage {...routeProps} />}
/>
Decorated with the withRouter Higher Order Component.
Example:
import { withRouter } from 'react-router-dom';
class ResultPage extends Component {
...
}
export default withRouter(ResultPage);
With the route props passed/injected, the ResultPage component should now be able to access a this.props.location.state?.data value passed in route state from the product page.

bind global variable on a component with :key

I've work on a Vue project, I have a login page which redirect to the Home page when the user is logged.
The thing is that I need to update/re-render the header component when the user is on the Home page.
So I've created a global variable in the main.ts:
Main.ts
Vue.prototype.isLogin = false;
I use this global value as my key for my header:
App.vue
<template>
<div id="app" class="container">
<e-header v-bind:key="isLogin" />
<div class="alert-box">
<div class="alert-list">
<e-alert
v-for="(notif, index) in $store.state.notifications"
:key="index"
:type="notif.type"
#dismissAlert="dismissAlert(index)"
>
{{ notif.message }}
</e-alert>
</div>
</div>
<router-view />
</div>
</template>
And on the Login component, in my login() methods:
Login.vue
AdminApi.login(this.email, this.password).then(() => {
this.loaderActive = false;
this.isLogin = true;
});
The problem is when the user login successfully and redirected on the Home page, the header component doesn't update, do I need to use prop instead of a global variable in my App.vue?
Vue updates it stuff when it detects that the data it depends on changes. For Vue to detect that it changes, the data needs to be reactive.
Something on the prototype chain is not, and I think you are over-complicating things by using the prototype chain for that. To manage a global state, just use a Vuex store. (docs)
You would then use ...mapGetters(['isLoggedIn']) in your computed property, this.$store.commit('loggedIn', true) or something along those lines in your Login.vue file.

How to make Laravel/Vue implementation show individual entry

I'm trying to integrate Laravel with Vue, and further down the line Nuxt, in the hope that I can integrate snazzy page transitions like the ones shown on http://page-transitions.com into my websites.
I've been reading a tutorial about using Vue with Laravel; https://scotch.io/tutorials/build-a-guestbook-with-laravel-and-vuejs, and I was pleased to find that Laravel ships with a Vue implementation, so I thought there'd be quite a lot of info on how to use the two in combination, but there doesn't seem to be.
I completed the tutorial and made the guestbook as it was described. I'm now trying to build upon that.
Specifically, Im trying to create individual pages for each of the guestbook entries.
I do have quite a bit of experience using Laravel, but only what I've described above with Vue.
So, in order to create the individual pages, I've created a new route in the routes/web.php file;
Route::get('signature/{id}','SignaturesController#show')->name('signature');
I've then created a new code block in app/Http/Controllers/SignaturesController.php to deal with this request;
public function show()
{
return view('signatures.signature');
}
I've created the specified view in resources/views/signatures/signature.php;
#extends('master')
#section('content')
<div class="container">
<div class="row">
<div class="col-md-12">
<signature></signature>
</div>
</div>
</div>
#endsection`
And I've created the vue file that should integrate with this view in resources/assets/js/components/Signature.vue;
<template>
<h1>Signature</h1>
</template>
<script>
export default {
}
</script>
Finally, I've registered the component in resources/assets/js/app.js and reran npm run dev.
This has worked to an extenet, I can view the file at the expected url; http://transitions.localhost/signature/1.
My question is, how do I get the data related to the signature with the ID of 1 into the page? I can't even echo out {{ id }} or {{ signature }}.
Any other resources that you've found helpful regarding this subject would also be greatly appreciated. Thanks for taking the time to read through all of that, does anyone know where I go from here?
You will need to pass the data to your vue component
Maybe something like this?
In your view:
#section('content')
<div class="container">
<div class="row">
<div class="col-md-12">
<signature :signature="{{ $signature }}"></signature>
</div>
</div>
</div>
#endsection
In your vue component:
<template>
<h1>This signature has the ID of: {{ signature.id }}</h1>
</template>
<script>
export default {
props: ['signature']
}
</script>

Jquery and Vue, .html() is not working

I have been playing with the Vue tutorial Here and I have added a simple Jquery .html function. However it is not working. I have added the jQuery plugin, and there are no errors in the console. I have my "App" component defined like this:
<template>
<div id="app">
<div id="mainMenu"> Hello </div>
</div>
</template>
<script>
import * as start from './assets/scripts/start.js'
export default {
name: 'app',
created: start.loadMainNavigation()
}
</script>
and my loadMainNavigation function like this:
function loadMainNavigation() {
$('#mainMenu').html("ASERFDASRF");
console.log("In load Nav");
}
I can see the "In load Nav" in the console. No errors, but the DIV still has the original "Hello" - What am I doing wrong?
The reason the content doesn't change is that, at the time you are executing your function, the component has not yet been rendered to the DOM. The DOM is not rendered until the mounted event.
Beyond that, however, you need to be careful when you are integrating jQuery and Vue, or avoid it altogether. The idiomatic Vue way to do this would be something like this.
console.clear()
new Vue({
el: "#app",
data:{
message: "Hello"
},
created(){
this.message = "ASERFDASRF"
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<div id="app">
<div id="mainMenu"> {{message}} </div>
</div>
There are a few times when you might mix jQuery and Vue (when you want to use a jQuery plugin for which there is no Vue counterpart, for example) but typically, there is almost always a way to do what you want without jQuery.

Categories