udpate #2: It is not a bug, it was my own fault. I extended the BaseComponent by a extend() method as well as putting extends: BaseComponent into the options. I thought I needed both, but extends: BaseComponent in the options seems to be enough.
So the double "extend" has apparently duplicated the watcher which lead to the strange behavior I documented in my question.
update: I found out what causes this problem: The watcher seems to be duplicated, because it's in a BaseComponent which is extended by the Component which is used in my example.
so export default BaseComponent.extend({ name: 'Component', ...}) seems to duplicate the watch object instead of "merging" it - there is now one in the BaseComponent (where it is implemented initially) and one in the Component - and of course both react to prop-updates.
This seems to be a bug IMHO.
Using vue-cli with single file components.
I am setting a prop in one component via a method:
<template>
<div>
<other-component :my-object="myObject" />
</div>
</template>
<script>
export default (Vue as VueConstructor).extend({
data() {
return {
myObject: null
}
},
methods: {
actionButtonClicked(action, ID) {
console.log('actionButtonClicked');
this.myObject = {
action: action,
ID: ID
}
}
}
});
</script>
then I am watching the prop in the other component with a watcher - but watch gets called twice on every execution of the method.
<script>
export default (Vue as VueConstructor<Vue>).extend({
/* ... */
props: {
myObject: {
type: Object,
default: null
},
watch: {
'myObject.ID'(value, oldValue) {
console.log('watcher executed');
}
}
/* ... */
});
</script>
so in the console i get the output:
actionButtonClicked
watcher executed
watcher executed
.. every time the method gets called.
I already tried all different variants of watchers - for example with deep: true + handler. but this all didn't change anything about the watcher being called twice.
In my case, I have the "watch" in a component and use the component twice on the page, so the "watch" is being registered twice.
Hopefully, it'll help somebody that has the same problem.
My watcher was duplicated because I was extending my BaseComponent in two ways:
by the extend() method of the component itself
by putting extends: BaseComponent into the options of the "outer" component
I thought you needed to use both pieces of code to extend another component, but apparently this is wrong and can lead to bad side effects.
You might want to check your component tree using a tool like the vue console or manually follow the list of imported components on your page in order to ensure no component is inadvertently imported twice.
I had a modal in the default layout, but wanted to display the modal body/main contents on a certain page (i.e without being triggered in a modal). I ended up importing both, and vue correctly treated them as separate instances. Which meant weird behavior like the modal taking two clicks to disappear, and of course, their watchers getting called twice
Related
I need to display a spinner in vue for every component (this is the requirement).
For that I think about to do v-if="loading" inside component HTML.
My question is how to detect when component is loading complete? (meaning after the DOM is rendered, and the data-bind is resolved to the DOM elements)
According to Vue lifecycle when update function is trigger then the render is complete.
So for that I'll need to implement for every component update function that change the loading to false. is there eazy way to do that? for example one place to write the update function? can I do that without implement extends? any sophisticated way to do that?
What I need for example is this.loading available in Vue instance.
Of course you can do it. Vue mixins come in rescue.
Mixins are a flexible way to distribute reusable functionalities for
Vue components. A mixin object can contain any component options. When
a component uses a mixin, all options in the mixin will be “mixed”
into the component’s own options.
Notice that you should use mounted hook if you want to track when the component is inserted into DOM.
Called after the instance has been mounted, where el is replaced by
the newly created vm.$el. If the root instance is mounted to an
in-document element, vm.$el will also be in-document when mounted is
called.
mixin.js:
export default {
data() {
return {
loading: true
}
},
mounted() {
console.log('I have been mounted')
this.loading = false
}
}
And then register that mixin globally, so it will be available in all components:
import mixin from './mixin'
Vue.mixin(mixin)
I have two components that conditionally render with v-if:
<Element v-if="this.mode === 'mode'"/>
<OtherElement v-if="this.mode !== 'mode'"/>
I have load-in animations for both components that I have under mounted(), that I only want to run the first time they are loaded. But with mounted, each time the component is recreated when this.mode changes, the animations trigger again. How can I avoid this?
You could wrap your components within a keep-alive element ..
<keep-alive>
<Element v-if="this.mode === 'mode'"/>
<OtherElement v-else />
</keep-alive>
If created() doesn't do the job, you should try to do a simple check in the parent element if this.mode was switched on and off before, save the result as a variable and pass that to the mounted hook and only run the animation if the mode wasn't switched before.
Using v-if, re-renders components every time the this.mode changes. Vue stores them in virtual DOM, but re-renders them if you use v-if.
If you have access to code for these components, consider using prop for v-show and watching it, in optional combination with emitso you can communicate between parent and child components, and set flag if you need it in child and in parent component if child component loads animation initially, to avoid loading it all over again.
This would be one of the child components:
<template>
<div v-show="mode === 'themode'">
</div>
</template>
<script>
export default {
props: {
mode: {
type: Boolean,
required: true,
twoWay: true
},
},
data() {
return {
animationLoaded : false,
}
},
mounted(){
},
watch: {
'mode' : function(){
if(this.mode === 'mode' && this.animationLoaded === false){
//load animation and set flag to true, to avoid loading it again.
this.animationLoaded = true;
this.$root.$emit('component-name:animation-loaded');
}
}
},
...
And putting child in parent component:
<child-component :mode.sync="mode"></child-component>
I had a similar problem, I only wanted something in my mounted to run on page load, but when I navigated away (using vue router) and back again, the mounted would run each time, re-running that code. Solution: I put a window.addEventListener('load', () => {}) in the created function, which runs that code instead.
The created function runs early on in the vue bootstrapping process, whereas the window.load event is the last event in the page load queue. As vue runs as a single page app, the window.load event will only fire once, and only after all the javascript (including vue) has got itself up and running (in a sense). I can route around my environment back and forth knowing that the initial build scripts would only be run when the page is actually re-loaded.
I have a feeling there is a more vue-tiful way of doing this, or I am otherwise missing something that vue-terans would chastise me for, but at this point vue has slapped me on the wrist far too many times for me to bother trying anymore. Hacky workarounds it is!
I have a parent component which is a flat list which contains a header HeaderComponent. This HeaderComponent is a custom component that I have created which contains 2 child components of its own. Whenever i refresh the list, I am passing a boolean to the HeaderComponent as props which get passed onto its own children, I am doing this so I can check if each component needs to fetch new data or not. The problem is that whenever the parent refreshes and sets a new state the constructors of the child components get called everytime. Shouldn't the constructor be called only the first time the parent initializes and then all further calls involve calling the shouldComponentUpdate method of the children in order to see if it needs an update or not.
Parent component
_renderHeader = () => {
return <HeaderComponent Items={this.state.Data} refresh={this.state.refresh}/>;
};
render() {
console.log("TAG_RENDER render called " + this.state.refresh);
return (
<FlatList
refreshing={this.state.refresh}
onRefresh={() => {
console.log("onRefresh");
this.setState({
refresh: true
}, () => {
this._fetchData();
});
}}
......
ListHeaderComponent={() => this._renderHeader()}
.......
/>
);
}
Header Component
export default class HeaderComponent extends React.Component {
constructor(props) {
super(props);
console.debug("HeaderComponent");
}
render() {
return (
<MainHeader Items={this.props.Items}/>
<SubHeader refresh={this.props.refresh}/>
);
}
}
The constructor of MainHeader and Subheader gets called whenever the parent component refreshes. Does this mean that it is creating new child components each time it refreshes because I can see the render of the children also being called multiple times.
Control your index.js file. If you see <React.StrictMode>, you should change to <>. This is solved my problem.
It should be like:
ReactDOM.render(
<>
<App/>
</>,
document.getElementById('root')
);
As correctly stated in the one of the answers , removing the strict mode fixes the issue. Coming to why it does that, its because the strict mode intentionally calls the 'render' method twice in order to detect potential problems.
React works in two phases:render and commit. Render phase checks and determines the new changes to be applied. And commit phase applies it.
Render phase lifecycle includes methods like : constructor, UNSAFE_componentWillMount,UNSAFE_componentWillReceiveProps, ...,render and few more.
The render phase is time consuming and is often broken into pieces to free up the browser. Render phase might be called multiple times before the commit phase(usually very fast).
Since the render phase methods are called more than once, its important that none of those method have any problems or side effects.
Thus just in order to highlight the possible side effects to make them easy to spot, react explicitly double invoke the render phase methods.
You can read more about this on :https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects :)
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate
methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
https://reactjs.org/docs/strict-mode.html
As stated in the site,
Note:
This only applies to development mode. Lifecycles will not be double-invoked in production mode.
We have a crazy DOM hierarchy, and we've been passing JSX in props rather than embedding children. We want the base class to manage which documents of children are shown, and which children are docked or affixed to the top of their associated document's window.
List (crazy physics writes inline styles to base class wrappers)
Custom Form (passes rows of JSX to Base class)
Base Class (connects to list)
Custom Form (passes rows of JSX to base class)
Base class (connects to list)
The problem is that we're passing deeply nested JSX, and state management / accessing refs in the form is a nightmare.
I don't want to re-declare every row each time, because those rows have additional state attached to them in the Base Class, and the Base Class needs to know which rows actually changed. This is pretty easy if I don't redeclare the rows.
I don't know how to actually deal with rows of JSX in Custom Form.
Refs can only be appended in a subroutine of render(). What if CustomForm wants to measure a JSX element or write inline CSS? How could that JSX element exist in CustomForm.state, but also have a ref? I could cloneElement and keep a virtual DOM (with refs) inside of CustomForm, or depend on the base class to feed the deeply-nested, mounted ref back.
I believe it's bad practice to write component state from existing state. If CustomForm state changes, and I want to change which rows are passed to BaseClass, I have to throttle with shouldComponentUpdate, re-declare that stage document (maintaining row object references), then call setState on the overarching collection. this.state.stages.content[3].jsx is the only thing that changed, but I have to iterate through every row in every stage document in BaseClass when it sees that props.stages changed.
Is there some trick to dealing with collections of JSX? Am I doing something wrong? This all seems overly-complicated, and I would rather not worsen the problem by following some anti-pattern.
Custom Form:
render () {
return <BaseClass stages={this.stages()}/>
}
stages () {
if (!this._stages) this._stages = { title: this.title(), content: this.content() };
return this._stages;
}
title () {
return [{
canBeDocked: false,
jsx: (
<div>A title document row</div>
)
}
}
content () {
return [{
canBeDocked: false,
jsx: (
<div>Hello World</div>
)
}, {
canBeDocked: true,
jsx: (
<div>Yay</div>
)
}
}
What I usually do is just connect the lower level components via Redux. This helps with not passing the state in huge chunks from the top-most component.
A great video course by one of the React creators, Dan Abramov: Getting started with Redux
Absolutely agree with #t1gor. The answer for us was to use REDUX. It changed the entire game for us. Suddenly a button that is nested 10 levels deep (that is, inside a main view, header, header-container, left side grid, etc, etc, deeper and deeper) into purely custom components, has a chance to grab state whenever it needs.
Instead of...
Parent (pass down state) - owns state vars
Child (will pass down again) - parent has state vars
Grandchild (will pass down a third time) - grandparent has state vars
Great Grandchild (needs that state var) - great grandparent has state vars
You can do...
Parent (no passing) - reads global state vars
Child
Grandchild
Great Grandchild - also reads same global level state vars without being passed...
Usually the code looks something like this...
'use strict'
//Importation of Connection Tools & View
import { connect } from 'react-redux';
import AppView from './AppView';
//Mapping -----------------------------------
const mapStateToProps = state => {
return {
someStateVar: state.something.capturedInState,
};
}
const mapDispatchToProps = dispatch => {
return {
customFunctionsYouCreate: () => {
//do something!
//In your view component, access this by calling this.props.customFunctionsYouCreate
},
};
}
//Send Mappings to View...
export default connect(mapStateToProps, mapDispatchToProps)(AppView);
Long story short, you can keep all global app state level items in something called a store and whenever even the tiniest component needs something from app state, it can get it as the view is being built instead of passing.
The issue is having content as follows, and for some reason not being able to effectively persist the child instances that haven't changed (without re-writing the entire templateForChild).
constructor (props) {
super(props);
// --- can't include refs --->
// --- not subroutine of render --->
this.state = {
templateForChild: [
<SomeComponentInstance className='hello' />,
<AnotherComponentInstance className='world' />,
],
};
}
componentDidMount () {
this.setState({
templateForChild: [ <div className='sometimes' /> ],
}); // no refs for additional managing in this class
}
render () {
return ( <OtherManagerComponent content={this.state.templateForChild} /> );
}
I believe the answer could be to include a ref callback function, rather than a string, as mentioned by Dan Abramov, though I'm not yet sure if React does still throw a warning. This would ensure that both CustomForm and BaseClass are assigned the same ref instance (when props.ref callback is executed)
The answer is to probably use a key or createFragment. An unrelated article that addresses a re-mounting problem. Not sure if the fragment still includes the same instances, but the article does read that way. This is likely a purpose of key, as opposed to ref, which is for finding a DOM node (albeit findDOMNode(ref) if !(ref instanceof HTMLElement).
I have a component with the following rendering (computed props). It works and shows the text as supposed to for blopp but nothing for blipp. In the final version, I want it to produce a list of strings brought from the state of the store and serve as blipp.
export default {
computed:{
blopp: function(){ return "ghjk,l"; },
blipp: function(){ return this.$store.getters.getBlipp(); }
}
}
It's rendered based on the following template.
<template>
<div>
...
<div v-bind:blopp="blopp">{{blopp}}</div>
<div v-bind:blipp="blipp">{{blipp}}</div>
</div>
</template>
The implementation of the store looks like this bringing the getters to the open forum.
...
const state = { blipp: [], ... };
const getters = {
getBlipp: function() { return state.Blipp; }, ...
}
export default new Vuex.Store({ state, mutations, actions, getters });
The second component gets no value in it at all and I'm not sure where to look for the cause.
I probably set it up incorrectly but it's a lot of moving parts and a bit hard to diagnose for the ignorant me. When I try to run the following in the console,
temp.$store.getters
I get an object which lists the getters like this.
...
blipp:(...)
get blipp: function()
__proto__: Onject
Not certain what to do with that info... It appears to be a function but when I try to invoke it, it says it's undefinied.
getters function in a similar manner to states. Therefore to resolve them you call a parameter not a method, i.e.
blipp: function() { return this.$store.getters.getBlipp }
In this case you probably want to rename getBlipp to simply blipp
I put together a JSFiddle which shows the various ways you can interact with vuex's store, hope it helps:
Example Vuex JSFiddle