Do something after fetch and mounted complete - javascript

I have a simple nuxt.js component like the one below.
Chart is a component that has a method which will receive the data that is fetched in fetch().
If I simply call that method after await fetch('...') I get an error when it's rendered on client-side since the Chart component has not yet been mounted. How could I go about to do something after fetch AND mounted?
And I can't do it in mounted() because then I can't be sure that the fetch is complete.
<template>
<div>
<!--Custom component-->
<Chart ref="chart"/>
</div>
</template>
<script>
export default {
data(){
return {
chartData: []
}
},
async fetch() {
this.chartData = await fetch('https://api.mocki.io/v1/b1e7c87c').then(res =>
res.json()
)
this.$refs.chart.insertSeries(this.chartData) // doesn't work because Chart is not mounted yet.
},
}
</script>

The preferred way of handling this situation would be to use a prop so that <Chart> can handle the data itself, and watch the prop in the child.
Parent
<Chart :chart-data="chartData" />
Chart
export default {
props: ['chartData'],
watch: {
chartData(newValue) {
if(newValue.length) {
this.insertSeries(newValue);
}
}
},
...
}
Variation: You could use v-if instead of a watch:
Parent
<Chart v-if="chartData.length" :chart-data="chartData" />
Chart
export default {
props: ['chartData'],
created() {
this.insertSeries(this.chartData); // `chartData` is guaranteed to exist
}
...
}
Note: There is a slight difference that can emerge between these two options. Imagine you wanted a loading animation while chart data was loading.
With the first option, since the component is shown immediately, the loading functionality would have to be put in the child. In the second option, it would be put in the parent (in a v-else).

Related

nuxtjs : how to force page reload and call asyncData()?

In my NuxtJS SSR project with bootstrap-vue as frontend :
I have some page with template and default component
In component there is asyncData(context) function that makes some deal before component render and mounts
Everything is working fine, except only one thing, I need reload (or refresh) a page after some interval automatically and call asyncData again. Standard js window.location.reload() is not good solution, because it`s reloads fully page. I want refresh in vue style, when only changed components re-rendres. I tried to call $nuxt.refresh() but no any effect
Some code example (cut from real project ) , page "index.vue" :
<template>
<div>
<b-tabs content-class="mt-3">
<b-tab title="Test" active><shdashboard searchtype="test"> </shdashboard></b-tab>
</b-tabs>
</div>
</template>
<script>
export default {
name : "index",
async asyncData (context) {
if(process.server)
{
const host = "http://SOMEHOST";
const http = context.$http;
const data = await http.$get(url);
console.log('in server render')
/*
some logic
commit store and prepare data fot b-tab component
*/
}
},
methods : {
refresh() {
console.log("method refresh")
this.$nuxt.refresh(); // Not any effect.
}
},
mounted() {
console.log("page mounted");
setInterval(()=>{
/*
I need to reload page every 15 sec, and call asyncData() to get new values for
<b-tab> component
*/
this.refresh();
},15000);
}
</script>
What do I wrong ?
One way, if I understand your issue correctly, is to use the Vuex store for storing whatever you are fetching in fetch/asyncData. That way you don't need to worry about the internals of Nuxt.js, and what hooks are triggered when.
I.e. something like this:
export default {
computed: {
someData () {
return this.$store.state.someData
},
},
async asyncData ({ store, $axios }){
let data = await $axios.$get(endpoint)
store.commit('setSomeData', data)
},
}
Then just use {{ someData }} in your template!

How to set watcher for router-view i vue

I'm begginer in vue and i can't resolve my problem with VueRouter.
I got main app component like
<template>
<div>
<Header />
<router-view />
<Footer />
</div>
</template>
One of my Router components has an function to get data from database.
import axios from 'axios';
export default {
name: 'ComponentName',
data() {
return {
dataFromDatabase: []
}
},
methods: {
getData: function() {
// Axios get function to get data from database and add it to this.dataFromDatabase array
}
},
created() {
this.getData();
}
}
Given data are based on url params and it should be changeable when clicking on link that are in header or in other places in a whole app. The problem is that it cannot change if the component won't reload. I know that the problem is that function is called after component is created and is not called again.
So my question is:
Is there any way to watch for url params changes (adding this.$route.params.param to watch() function is not working). Maybe there is a better way to set up my routes or other way to call a function except of created() function or maybe component would reload everytime the link change. As i said links to this can be everywhere even in components that are not setted up in Router
You probably just need watch which by the way is not a function but an object with methods inside
watch: {
'$route'() {
// do something
}
}
you can use a smart watcher that will be watching since the component was created:
watch: {
'$route': {
immediate: true,
handler(newValue, oldValue) {
// ...
}
}
}

React/Preact: how to temporarily avoid updating the DOM for a certain component?

I'm using server-side rendering with Webpack's code-splitting. The server returns the HTML for the component. However, when React initializes, since I'm using code-splitting, the React component I want to render isn't downloaded yet. Typically, I'd want to display a loading screen. However, the HTML for the component is already rendered, so I don't want to replace it with a loading screen.
Is there a way to get React to temporarily ignore the component and not update the DOM?
The component looks something like this:
export default class SomeRoute extends Preact.Component {
constructor() {
super();
this.state = {
Component: null,
};
}
componentDidMount() {
if (!this.state.component) {
this.props.componentLoader().then(Component => this.setState({ Component }));
}
}
render({}, { Component }) {
if (!Component) {
return (
<p>Loading...</p>
);
}
return (
<Component />
);
}
}
The output of <Component /> is already returned by the server.
You can use shouldComponentUpdate(). When you return false it will not update the component.
shouldCompnentUpdate(nextprops,nextstate){
return Boolean(this.state.Component)
}

Vue.js emit event from child component not catched

I have a child component which fetch some data from my server, before fetching I change the loading status to true and I want to set it to false after the fetch is completed. So I do something like that in my child component:
mounted() {
this.$emit('update:loadingMessage', 'Loading version options from Artifactory...');
this.$emit('update:isLoading', true);
this.fetchVersions();
},
methods: {
fetchVersions() {
const promises = [
this.$http.get(`${process.env.API_URL}/version/front`),
this.$http.get(`${process.env.API_URL}/version/back`),
];
Promise.all(promises)
.then((values) => {
// Do some stuff
})
.then(() => {
this.$emit('update:isLoading', false);
})
.catch(requestService.handleError.bind(this));
},
},
And in my parent component I listen to this event like that:
<version-selector
:form="form"
#update:loadingMessage="updateLoadingMessage"
#update:isLoading="updateLoadingStatus"
:isSnapshotVersion="isSnapshotVersion">
</version-selector>
Finally in the updateLoadingStatus I set the isLoading value to true or false accordingly.
updateLoadingMessage(message) {
this.$log.debug(message);
this.loadingMessage = message;
},
updateLoadingStatus(status) {
this.$log.debug(status);
this.isLoading = status;
},
This is useful to display or not my loading component:
<loading
v-if="isLoading"
:loadingMessage="loadingMessage"
:isGiphy="true">
</loading>
My problem is that the first emit is working and the isLoading value is set to true but the second one is not working and my isLoading value stay to true forever... In my method updateLoadingStatus I log the status value and I see that this method is just called once.
I solved the problem by using v-show instead of v-if in my template.
<loading
v-show="isLoading"
:loadingMessage="loadingMessage"
:isGiphy="true">
</loading>
I know this is an old question, but I stumbled into a similar situation today, and finally understood why the mechanism was working with v-show, and not v-if.
If you get the following <template> tag:
<div>
<component-a v-if="isLoading" />
<component-b v-else #set-loading-status="setIsLoading" />
</div>
And the following <script> tag:
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
export default {
name: 'ParentComponent',
components: { ComponentA, ComponentB },
data() {
return {
isLoading: false,
}
},
methods: {
setIsLoading(isLoading) {
this.isLoading = isLoading
},
},
}
It seems fine, right? You can catch the set-loading-status event from the <component-b> and set it to true. But you can't catch it again to set it back to false.
But, let's take a look in the official Vue docs about v-if and v-show:
v-if is “real” conditional rendering because it ensures that event listeners and child components inside the conditional block are properly destroyed and re-created during toggles.
Now you can see that the component-b gets destroyed when isLoading is set to true, and you won't be able to catch the emitted event to change it back to false.
So, in this particular case, you must use v-show to handle the loading status.

Clone a component that has already been mounted

I am trying to build an app that uses drag-and-drop behaviour, and the component being dragged needs to be cloned elsewhere in the DOM. Since the component is already mounted, trying to mount it again causes the browser to hang.
Trying to use cloneWithProps results in a Cannot read property 'defaultProps' of undefined error.
Here's a testcase:
var TestCase = React.createClass({
getInitialState () {
return {
draggingItem: null
}
},
render () {
return <div>
<ExampleComponent onClick={this.setDraggingItem} />
{this.state.draggingItem}
</div>
},
setDraggingItem (component) {
// This gives `Cannot read property 'defaultProps' of undefined`
//React.addons.cloneWithProps(component)
// This crashes the browser
//this.setState({ draggingItem: component })
}
})
var ExampleComponent = React.createClass({
render () {
return <div onClick={this.handleOnClick}>Hello World</div>
},
handleOnClick (event) {
this.props.onClick(this)
}
})
React.render(<TestCase />, document.body)
Of course I could simply clone component.getDOMNode() in setDraggingItem, but it really seems like rendering the component or calling cloneWithProps should work?
The two things you need to create an element is: the component class (e.g. ExampleComponent) and its props. cloneWithProps is only to be used in render and only with an element coming from props which was created in another component's render. You shouldn't save elements, or pass them around other than to other components in render. Instead, you pass around objects (props) and component classes.
Since you need to know the props and component class to render it in the first place, you can handle all of this in TestCase.
var TestCase = React.createClass({
getInitialState () {
return {
draggingItem: null,
draggingItemProps: null
}
},
render () {
return <div>
<ExampleComponent onClick={this.setDraggingItem.bind(null,
/* the component class */ ExampleComponent,
/* the props to render it with */ null
)} />
{
this.state.draggingItem && React.createElement(
this.state.draggingItem,
this.state.draggingItemProps
)
}
</div>
},
setDraggingItem (component, props, event) {
this.setState({ draggingItem: component, draggingItemProps: props })
}
});
var ExampleComponent = React.createClass({
render () {
return <div onClick={this.handleOnClick}>Hello World</div>
},
// just defer the event
handleOnClick (event) {
this.props.onClick(event)
}
});
If you wish to make these valid outside this TestCase component, ensure there aren't any functions bound to TestCase in the props. Also ensure there's no children prop with react elements in it. If children are relevant, provide the {componentClass,props} structure needed to recreate them.
It's hard to tell what your actual requirements are, but hopefully this is enough to get you started.
You need be sure you're creating a new component with the same props, not mount the same one multiple times. First, setup a function that returns an instantiated components (easier to drop JSX here):
function getComponent(props) {
return ExampleComponent(props);
}
Then in your TestCase render:
return (<div>
{ getComponent({ onClick: this.setDraggingItem }) }
{ this.state.draggingItem }
</div>);
That will create the first component. Then to create a clone:
setDraggingItem(component) {
var clone = getComponent(component.props);
}
This deals with the cloning part. You still have some dragging and rendering to figure out.

Categories