I am a junior developer who lacks experience, so I apologize if my question showcases signs of sheer ignorance. My title may not be expressive of the problem I face, so I shall do my best to be descriptive.
In my project, I am making use of a 3rd party component (a dropdown menu), which I would like to modify in my application. I don't want to fork and edit their code since I would like to pull the latest styling changes since my modification is only slight, being that I would like to add some text next to the dropdown icon.
Here is a (simplified) version of the code.
<template>
<overflow-menu
ref="overflow_menu"
>
<overflow-menu-item
v-for="item in overflowMenuItems"
:id="item.id"
:key="item.name"
>
{{ item.tabName }}
</overflow-menu-item>
</overflow-menu>
</template>
<script>
import { OverflowMenu, OverflowMenuItem } from '#some-library/vue'; //Don't have control of the implementation of these components.
export default {
name: 'CustomOverflowMenu',
components: {
OverflowMenu,
OverflowMenuItem,
},
props: {
overflowMenuItems: Array,
label: String,
},
mounted() {
this.injectOverflowMenuLabel();
},
methods: {
injectOverflowMenuLabel() {
const overflowMenuElement = this.$refs.overflow_menu.$el.firstChild;
const span = document.createElement("span");
const node = document.createTextNode(this.$props.label);
span.appendChild(node);
overflowMenuElement.insertBefore(
span,
overflowMenuElement.firstChild,
);
}
}
};
</script>
It functionally works ok, however, it doesn't seem a particularly elegant solution, and I feel as if I could be doing it in a more "Vuey" way. I also am greeted with a Vue warning of: Error in created hook: "TypeError: Cannot read property 'insertBefore' of undefined. This ultimately means I am not able to mount my component and unit test my custom overflow menu.
Is there a way to get this functionality, but in a more maintainable manner. I would either like to simplify the logic of the injectOverflowMenuLabel function, or perhaps there is a completely alternative approach that I haven't considered.
Would appreciate any help, you lovely people.
Thanks,
Will.
Related
I have serious problems with memory leaks in a rather large Vue 3 application (TS, Vuex).
It has a hierarchical structure with a large number of sub-components which amplifies the problems.
I managed to reproduce the issue in a very simple way and search your advice and opinion on that.
Reproduction:
Create a fresh Vue 3 application with Vue CLI (4.5.15) with TypeScript & Babel (maybe not necessary, but similar to my application).
Replace the Home.vue with the following code:
<template>
<div>
<button #click="clearArray">clearArray</button>
<button #click="fillArray">fillArray</button>
<div v-for="i in testArray" :key="i">{{ i }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "LeakDetector",
beforeUnmount() {
this.clearArray();
},
setup() {
let testArray = ref<number[] | null>(null);
const clearArray = () => {
testArray.value = null;
};
const fillArray = () => {
testArray.value = Array.from({ length: 100000 }, (v, k) => k);
};
return { testArray, clearArray, fillArray };
},
});
</script>
I was thinking that Vue would take care of the onmounting/cleaning of regular HTML elements. But if I now
click the fillArray button
go to About page and back to Home page
the memory of those many div elememts cannot be freed by garbage collection. The call of clearArray in the beforeUnmount hook does not help.
But if I
click the fillArray button
click the button clearArray and then
change the route and back
the garbage collection works.
I checked in chrome dev tools (memory tab) and I can find those detached elements there in case the array wasn't cleared. Also in Chrome Task Manager memory piles up quickly if the clearArray button isn't clicked before route change.
So:
Am I doing / understanding / analysing something wrong?
Is it necessary to do some "cleaning" for v-for-tags? (I didn't find anything in the vue docs)
Is it a misbehavior / bug inside of vue?
UPDATE:
I tried a production build and the problem disappeared. That made me think of Vue.js devtools (6.0.0 beta 21) running in my browser. So I disabled that and it made the problem in the reproduction example disappear... I'll analyse that in the application and give a final update in case anybody else faces that issue. Thanks so far!
CONCLUTION:
I was on a wrong track there. The memory leak of the real application emerged from the following 2 things (and a bit of missing experience in interpreting the heaps):
Usage of const removeWatcher = this.$watch(...) which does not seem to remove the listeners when calling the removeWatcher() function. So replaced those with different functionality. I didn't debug that deeply. Might be another issue.
I forgot an .off() when using mitt for an EventBus.
As you want to build complex component, it would be great if you can wrap any DOM with component such as "lazy-load" component with condition (#Prop() condition: boolean) so to illustrate what I want:
<lazy-load condition={some boolean condition, like certain link get clicked and section is now active}>
<data-fetch>
</data-fetch>
</lazy-load>
in this example, "data-fetch" will make a HTTP call to grab some large data, and I want to defer this component added to DOM until condition we specify in the lazy-load component to be true.
So I started to implement render() of lazy-load component as something along the line of
#Prop() condition: boolean;
render() {
if(!this.condition) {
return null;
}
return (
<slot/>
);
}
and try to use it as
<lazy-load condition={false}>
<data-fetch>
</data-fetch>
</lazy-load>
but no matter what I tried, data-fetch component get added to DOM (and while we can set visibility to hide element, we would waste HTTP call) I understand I can put the same condition in the data-fetch itself and then not make a fetch call when condition is false, but if possible I want generic wrapper component to achieve this (if you are familiar with AngularJS or Angular, I want to find a way to do equivalent of ng-if and *ngIf off of generic wrapper component)
Maybe this is a limitation due to how "slot" tag supposed to work? (also, I'm using it with #Component({shadow: false}) so I know I'm not using standard shadowDOM from the web component spec so maybe what I'm trying to do is not feasible?
Thank you very much for your time in advance to even read this question and I appreciate any help I can get. I feel if we can do this, we might be able to build component that can quickly differ loading until whenever we feel it should load/render.
Yeah it's an issue with not using shadow: true, because in the polyfill the slotted content just becomes part of the light DOM (but gets placed where the slot element is). Beware that even if you enable Shadow DOM, it'll still fallback to the polyfill if the browser doesn't support it. You could raise an issue about this in Github but I'm not sure if/how it would be possible to solve this "dynamic slot" problem.
But I think you can take a simpler approach:
{myCondition && <data-fetch />}
That way the data-fetch element will only be added once the condition becomes true.
You could also refactor this into a functional component:
import { FunctionalComponent } from '#stencil/core';
interface Props {
if: boolean;
}
export const LazyLoad: FunctionalComponent<Props> = ({ if }, children) =>
if && children;
import { LazyLoad } from './LazyLoad';
<LazyLoad if={condition}>
<data-fetch />
</LazyLoad>
I want to use react-id-swiper library which exports a component named Swiper.
This is its render method:
render() {
const { containerClass, wrapperClass, children, rtl } = this.props;
const rtlProp = rtl ? { dir: 'rtl' } : {};
return (
<div className={containerClass} {...rtlProp}>
{this.renderParallax()}
<div className={wrapperClass}>
{React.Children.map(children, this.renderContent)}
</div>
{this.renderPagination()}
{this.renderScrollBar()}
{this.renderNextButton()}
{this.renderPrevButton()}
</div>
);
}
}
This component perfectly matches my needs, except that I need to place pagination in an outer place (outside of the containerClass element).
One possible solution is to inherit Swiper class and change only it's render method. However Facebook docs are explicit to not use inheritance and use composition instead.
What's the best way to move the pagination outside of containerClass?
Can it be done with composition?
As it stands there is not much you can do, since this component is render specifically in this structure align with a secondary library. In the rebuildSwiper function you can see it passing it the dom elements to this other library to bind and handle all interactions.
Depending on the flexibility of the secondary app you could move the render order. To do so I would first evaluate what the capabilities are of the inner library, then fork this repo and publish updates using the a scoped package such as #<username>/react-id-swiper.
I think composition would be well and good, but because you're using a library and not able to design the source class, I think inheritance is your only option.
I would recommend simply extending the Swiper class. You can write your new render method in a way that would allow for composition reuse moving forard if you want to follow these recommended practices, but I think it's safe to trust your gut in this case.
I'll be watching this question and am curious to see how others would approach it and what you're ultimate take is, too.
So let's say I have a component called ImageGrid and it is defined as below:
window.ImageGrid = React.createClass({
render: function() {
return (
<div className="image-grid">
<ImageGridItem />
</div>
);
}
});
As you can see it includes a child react component called ImageGridItem. Which is defined below.
window.ImageGridItem = React.createClass({
render: function() {
return (
<div className="item-container">something</div>
);
}
});
This works fine as long as both are directly properties of window. But this is kind of horrible so I'd like to group up all my react components under a namespace of window.myComponents for example.
So the definitions change to:
window.myComponents.ImageGrid = React.createClass({...});
window.myComponents.ImageGridItem = React.createClass({...});
The problem now is that as ImageGrid refers to <ImageGridItem /> in it's render() function, the JS version of this gets compiled out to JS as ImageGridItem() which of course is undefined since it's now actually myComponents.ImageGridItem() and react complains it can't find it.
Yes I realise I can just not write JSX for that component include and manually do myComponents.ImageGridItem({attr: 'val'}) but I'd really prefer to use the JSX html syntax shortcut as it's much easier to read and develop with.
Are there any ways to get this to work while still using the <ImageGridItem /> syntax for children? And if not is it possible to define the JS namespace within the JSX?
This pull request was just merged:
https://github.com/facebook/react/pull/760
It allows you to write things like <myComponents.ImageGridItem /> in React 0.11 and newer.
That said, a proper module system is the recommended way to manage dependencies to avoid pulling in code that you don't need.
Currently, there isn't a way to do this. Namespacing with JSX is on the todo list.
Most people use some kind of module system (browserify, webpack, requirejs), which replace namespacing and allow components to be used easily. There are a lot of other benefits, so I very much recommend looking into it.
I recently learned about Facebook/Instagram's app framework for JavaScript called "React" and wanted to look more into it. However, I found myself getting conflicting search results, as there is another library of a similar name. So, my question is this: Are there similarities between the two, or could someone do a better job at naming?
React
http://facebook.github.io/react/index.html
react.js
http://www.reactjs.com/
react.js is a language extension that lets you have automatic binding for values. The name React comes from the automatic updates of the values when one changes.
react( "soonChanged = undefined" );
react( "reactive = soonChanged + 5" );
react( "reactive2 = reactive * 10" );
react( "soonChanged = 10" );
// Changes the value of reactive from "undefined5" to 15
// and of reactive2 from NaN to 150
React on the other end is a framework to build user interfaces. The name React comes from the automatic update of the UI when some state changes.
/** #jsx React.DOM */
var converter = new Showdown.converter();
var MarkdownEditor = React.createClass({
getInitialState: function() {
return {value: 'Type some *markdown* here!'};
},
handleChange: function() {
this.setState({value: this.refs.textarea.getDOMNode().value});
},
render: function() {
return (
<div className="MarkdownEditor">
<h3>Input</h3>
<textarea
onChange={this.handleChange}
ref="textarea"
defaultValue={this.state.value} />
<h3>Output</h3>
<div
className="content"
dangerouslySetInnerHTML={{
__html: converter.makeHtml(this.state.value)
}}
/>
</div>
);
}
});
React.renderComponent(<MarkdownEditor />, mountNode);
A bit more comparison added to above:
react.js
can also do auto UI updating, but you need to manually code that:
/* **syntax aren't correct but the idea eg:
(you can find it in their doc for correct syntax) */
react( "yourReactiveComponent = 1" )
react( "$('body').html( yourReactiveComponent )")
/* now u change that variable */
react( "yourReactiveComponent = 4" )
/* it will automatically call that jquery function to change the html from 1 to 4 */
it is not really a framework, just an PUBSUB event helper, that subscribe and publish on variable change
awesome for small applications
facebook React
this is a framework
they will deal with a lot of cross browser issues
they have stuff worked on optimising performance such as an virtual DOM
has their own cool markup syntax compiler JSX and JSXtransformer, so you can write declarative HTML in a JS file and than transform it into a real JS file
they are component focus, so you will have both js and html that related to one component managed within their own code block, making it so much easier to change DOM stuff without switching all the time from JS to TPL and vice versa, This is the part where i love the most, I've been coding like this for some time, but never have a framework that does this for me, now I do :)
now http://www.reactjs.com/ redirect to https://facebook.github.io/react/
It's domain seems to be acquired!
at this time 2016-08-23, the google search result of react.js are all about facebook react
i don't know what you said react.js is, it seems disappeared!
from the answer above
react.js is a language extension
i just think it seems that the old react.js is now http://reactivex.io/, i don't know right or wrong
you can see some comparison here: http://blog.getsetbro.com/js/FB-react-vs-reactiveJS-vs-ractiveJS-vs-reactiveUI-vs-reactiveX.html