How to access the property of data set? - javascript

I copied some plain HTML, CSS and JavaScript code from code pen, but as I am trying to convert into React, it says that it can't read properties of null reading (dataset)
JS code:
const wrapper = document.getElementById("wrapper");
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
const uniqueRand = (min, max, prev) => {
let next = prev;
while(prev === next) next = rand(min, max);
return next;
}
const combinations = [
{ configuration: 1, roundness: 1 },
{ configuration: 1, roundness: 2 },
{ configuration: 1, roundness: 4 },
{ configuration: 2, roundness: 2 },
{ configuration: 2, roundness: 3 },
{ configuration: 3, roundness: 3 }
];`your text`
let prev = 0;
setInterval(() => {
const index = uniqueRand(0, combinations.length - 1, prev),
combination = combinations[index];
wrapper.dataset.configuration = combination.configuration;
wrapper.dataset.roundness = combination.roundness;
prev = index;
}, 1000);
HTML code for this:
<div id="wrapper"ref={myContainer} data-configuration="1" data-roundness="1">
<div className="shape"></div>
<div className="shape"></div>
<div className="shape"></div>
<div className="shape"></div>
<div className="shape"></div>
<div className="shape"></div>
<div className="shape"></div>
</div>
I would have attached the CSS, but that is way too long to attach...
I tried using useRef, but that didn't work. I have to find some way to just figure out the work around for this.
Uncaught TypeError: Cannot read properties of null (reading 'dataset')

What very probably happens is that you query the wrapper <div> before it is mounted in DOM, hence your wrapper variable is null, hence the error message.
A very easy workaround would be to simple query the Element just before you need it, inside the setInterval:
setInterval(() => {
const index = uniqueRand(0, combinations.length - 1, prev),
combination = combinations[index];
const wrapper = document.getElementById("wrapper");
if (wrapper) {
wrapper.dataset.configuration = combination.configuration;
wrapper.dataset.roundness = combination.roundness;
}
prev = index;
}, 1000);
Demo: https://codepen.io/ghybs/pen/dyjKQGg

Related

bug from element showing

Hi all I have following code: my code
In code I am recieving data from backend, In my case I hardcoded that part (see below data)
const skills = [
{
id: 1,
name: "Html "
},
{ id: 2, name: "CSS" },
{ id: 3, name: "Scss" },
{ id: 4, name: "Bootstrap 4" },
{ id: 5, name: "JavaScript" },
{ id: 6, name: "Jquery" },
{ id: 7, name: "React JS" },
{ id: 8, name: "Angular " },
{ id: 9, name: "Vue.js " },
{ id: 10, name: "SQL" },
{ id: 11, name: "php" },
{ id: 12, name: "Laravel" }
];
I am counting all my skill names char length and if that length is greater then allowCharCount I am hiding all rest skills and showing in number how many skills is hided. That part is working.
let lengthCount = 0;
let maxIndex = 0;
const allowCharCount = 20;
const skill = [];
export const Skill = ({ data }) => {
if (data === undefined) {
return null;
} else {
data.map((item) => {
if (lengthCount <= allowCharCount) {
maxIndex = item.id;
skill.push(item);
}
lengthCount += item.name.length;
});
const mySkills = skill.map((perSkill) => (
<span key={perSkill.id} className="skillItem">
{perSkill.name}
</span>
));
return (
<div className="skillWrapper">
<div>{mySkills}</div>
{lengthCount > allowCharCount ? (
<div className="skillNumber">+{data.length - maxIndex}</div>
) : null}
</div>
);
}
};
but when my chars count is less then allowCharCount it's not working.
If I only have first 3 items (Html, CSS, Scss) I see following view
Html CSS Scss Html CSS Scss +0.
Please help me to fix this code for that case (if chars count is less than allowCharCount) I need to show only correct items with no any +0 count
You should rethink the way you write the component and code in general
You have to store dynamic variables inside the component
To save the result of array operations, you should either mutate state or create a new variable inside the component (which is more preferable in your case).
.map method return an array of values returned by callback inside of it. If you're not going to return anything, use .forEach instead
Use .length property instead of incrementing the size of array by yourself to avoid bugs.
The reason why you get duplicated elements is that you don't clear the array before the component updates, so the algorithm just pushes these values again.
Working code example:
export const Skill = ({ data }) => {
// here I invert condition to get the rest code out of brackets
if (!data) return null;
// all of the dynamic data should be inside the component
const failIndex = React.useMemo(() => {
let charCount = 0;
for (let i = 0; i < data.length; i++) {
charCount += data[i].name.length;
if (charCount > allowCharCount) return i;
}
return -1;
}, [data]);
// check if data has element that doesn't pass the condition
const dataBeforeFail = failIndex === -1 ? data : data.slice(0, failIndex + 1);
const hiddenSkillsCount = data.length - dataBeforeFail.length;
return (
<div className="skillWrapper">
<div>
{dataBeforeFail.map((perSkill) => (
<span key={perSkill.id} className="skillItem">
{perSkill.name}
</span>
))}
</div>
{hiddenSkillsCount > 0 && (
<div className="skillNumber">+{hiddenSkillsCount}</div>
)}
</div>
);
};

In an Observable notebook, unable to use a function from an npm library, perhaps because unable to import a function from the library's dependency

I want to reproduce the example in this npm library in an Observable notebook. I run the following in a cell and a block:
fit_data = {
let data = {
x: [0, 1, 2],
y: [1, 1, 1]
}
return data
}
{
const LM = require('ml-levenberg-marquardt#2.1.1/lib/index.js').catch(() => window["_interopDefault"]);
function sinFunction([a, b]) {
return (t) => a * Math.sin(b * t);
}
const options = {
damping: 1.5,
gradientDifference: 10e-2,
maxIterations: 100,
errorTolerance: 10e-3
};
let fittedParams = levenbergMarquardt(fit_data, sinFunction, options);
return fittedParams
}
and I get the error message TypeError: isArray is not a function, which I suspect is this function failing to be imported from the library's dependency.
I am importing the library by following this guide.
Everything looks fine, I guess you misspelled the import method name. Another suggestion is to break into cells. Here are the cells that worked for me
Here is how you import it
LM = await require('ml-levenberg-marquardt#2.1.1/lib/index.js').catch(
() => window["_interopDefault"]
)
Here is how you use it
{
const options = {
damping: 1.5,
gradientDifference: 10e-2,
maxIterations: 100,
errorTolerance: 10e-3
};
let fittedParams = LM(fit_data, sinFunction, options);
return fittedParams;
}
fit_data = {
let data = {
x: [0, 1, 2],
y: [1, 1, 1]
};
return data;
}
function sinFunction([a, b]) {
return t => a * Math.sin(b * t);
}

In Rx.js, how can I distinguish which stream triggers the combineLatest method?

I'm writing my own version of who to follow?. Clicking refreshButton will fetching suggestions list and refresh <Suggestion-List />, and closeButton will resue the data from suggestions list and refresh <Suggestion-List-Item />.
I want to let the closeClick$ and suggestions$ combine together to driving subscribers.
Demo code here:
var refreshClick$ = Rx.Observable
.fromEvent(document.querySelector('.refresh'), 'click')
var closeClick$ = Rx.Observable.merge(
Rx.Observable.fromEvent(document.querySelector('.close1'), 'click').mapTo(1),
Rx.Observable.fromEvent(document.querySelector('.close2'), 'click').mapTo(2),
Rx.Observable.fromEvent(document.querySelector('.close3'), 'click').mapTo(3)
)
var suggestions$ = refreshClick$
.debounceTime(250)
.map(() => `https://api.github.com/users?since=${Math.floor(Math.random()*500)}`)
.startWith('https://api.github.com/users')
.switchMap(requestUrl => Rx.Observable.fromPromise($.getJSON(requestUrl)))
Rx.Observable.combineLatest(closeClick$, suggestions$, (closeTarget, suggestions) => {
if (/* the latest stream is closeClick$ */) {
return [{
target: clickTarget,
suggestion: suggestions[Math.floor(Math.random() * suggestions.length)]
}]
}
if (/* the latest stream is suggestions$ */) {
return [1, 2, 3].map(clickTarget => ({
target: clickTarget,
suggestion: suggestions[Math.floor(Math.random() * suggestions.length)]
}))
}
})
Rx.Observable.merge(renderDataCollectionFromSuggestions$, renderDataCollectionFromCloseClick$)
.subscribe(renderDataCollection => {
renderDataCollection.forEach(renderData => {
var suggestionEl = document.querySelector('.suggestion' + renderData.target)
if (renderData.suggestion === null) {
suggestionEl.style.visibility = 'hidden'
} else {
suggestionEl.style.visibility = 'visible'
var usernameEl = suggestionEl.querySelector('.username')
usernameEl.href = renderData.suggestion.html_url
usernameEl.textContent = renderData.suggestion.login
var imgEl = suggestionEl.querySelector('img')
imgEl.src = "";
imgEl.src = renderData.suggestion.avatar_url
}
})
})
You can find it in JsFiddle.
You should note the comments in condition judgment, closeClick$ emits [{ target: x, suggestion: randomSuggestionX }], suggestions$ emits [{ target: 1, suggestion: randomSuggestion1 }, { target: 2, suggestion: randomSuggestion2 }, { target: 3, suggestion: randomSuggestion3 }]. Subsriber render interface according to the emitted data.
May there are some ways/hacks to distinguish the latest stream in combineLatest or elegant modifications?
I think the easiest way would be to use the scan() operator and always keep the previous state in an array:
Observable.combineLatest(obs1$, obs2$, obs3$)
.scan((acc, results) => {
if (acc.length === 2) {
acc.shift();
}
acc.push(results);
return acc;
}, [])
.do(states => {
// states[0] - previous state
// states[1] - current state
// here you can compare the two states to see what has triggered the change
})
Instead of do() you can use whatever operator you want of course.
Or maybe instead of the scan() operator you could use just bufferCount(2, 1) that should emit the same two arrays... (I didn't test it)

Vue.js computed property not updating

I'm using a Vue.js computed property but am running into an issue: The computed method IS being called at the correct times, but the value returned by the computed method is being ignored!
My method
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
}
The values printed out by the console.log statement are correct, but when I use filteredClasses in template, it just uses the first cached value and never updates the template. This is confirmed by Vue chrome devtools (filteredClasses never changes after the initial caching).
Could anyone give me some info as to why this is happening?
Project.vue
<template>
<div>
<div class="card light-blue white-text">
<div class="card-content row">
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.name" id="filter-name">
<label for="filter-name">Name</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.status" id="filter-status">
<label for="filter-status">Status (PASS or FAIL)</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.apkVersion" id="filter-apkVersion">
<label for="filter-apkVersion">APK Version</label>
</div>
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.executionStatus" id="filter-executionStatus">
<label for="filter-executionStatus">Execution Status (RUNNING, QUEUED, or IDLE)</label>
</div>
</div>
</div>
<div v-for="(klass, classIndex) in filteredClasses">
<ClassView :klass-raw="klass"/>
</div>
</div>
</template>
<script>
import ClassView from "./ClassView.vue"
export default {
name: "ProjectView",
props: {
projectId: {
type: String,
default() {
return this.$route.params.id
}
}
},
data() {
return {
project: {},
filter: {
name: "",
status: "",
apkVersion: "",
executionStatus: ""
}
}
},
async created() {
// Get initial data
const res = await this.$lokka.query(`{
project(id: "${this.projectId}") {
name
classes {
name
methods {
id
name
reports
executionStatus
}
}
}
}`)
// Augment this data with latestReport and expanded
const reportPromises = []
const reportMeta = []
for(let i = 0; i < res.project.classes.length; ++i) {
const klass = res.project.classes[i];
for(let j = 0; j < klass.methods.length; ++j) {
res.project.classes[i].methods[j].expanded = false
const meth = klass.methods[j]
if(meth.reports && meth.reports.length) {
reportPromises.push(
this.$lokka.query(`{
report(id: "${meth.reports[meth.reports.length-1]}") {
id
status
apkVersion
steps {
status platform message time
}
}
}`)
.then(res => res.report)
)
reportMeta.push({
classIndex: i,
methodIndex: j
})
}
}
}
// Send all report requests in parallel
const reports = await Promise.all(reportPromises)
for(let i = 0; i < reports.length; ++i) {
const {classIndex, methodIndex} = reportMeta[i]
res.project.classes[classIndex]
.methods[methodIndex]
.latestReport = reports[i]
}
this.project = res.project
// Establish WebSocket connection and set up event handlers
this.registerExecutorSocket()
},
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
},
methods: {
isFiltered(method, klass) {
const nameFilter = this.testFilter(
this.filter.name,
klass.name + "." + method.name
)
const statusFilter = this.testFilter(
this.filter.status,
method.latestReport && method.latestReport.status
)
const apkVersionFilter = this.testFilter(
this.filter.apkVersion,
method.latestReport && method.latestReport.apkVersion
)
const executionStatusFilter = this.testFilter(
this.filter.executionStatus,
method.executionStatus
)
return nameFilter && statusFilter && apkVersionFilter && executionStatusFilter
},
testFilter(filter, item) {
item = item || ""
let outerRet = !filter ||
// Split on '&' operator
filter.toLowerCase().split("&").map(x => x.trim()).map(seg =>
// Split on '|' operator
seg.split("|").map(x => x.trim()).map(segment => {
let quoted = false, postOp = x => x
// Check for negation
if(segment.indexOf("!") === 0) {
if(segment.length > 1) {
segment = segment.slice(1, segment.length)
postOp = x => !x
}
}
// Check for quoted
if(segment.indexOf("'") === 0 || segment.indexOf("\"") === 0) {
if(segment[segment.length-1] === segment[0]) {
segment = segment.slice(1, segment.length-1)
quoted = true
}
}
if(!quoted || segment !== "") {
//console.log(`Item: ${item}, Segment: ${segment}`)
//console.log(`Result: ${item.toLowerCase().includes(segment)}`)
//console.log(`Result': ${postOp(item.toLowerCase().includes(segment))}`)
}
let innerRet = quoted && segment === "" ?
postOp(!item) :
postOp(item.toLowerCase().includes(segment))
//console.log(`InnerRet(${filter}, ${item}): ${innerRet}`)
return innerRet
}).reduce((x, y) => x || y, false)
).reduce((x, y) => x && y, true)
//console.log(`OuterRet(${filter}, ${item}): ${outerRet}`)
return outerRet
},
execute(methID, klassI, methI) {
this.project.classes[klassI].methods[methI].executionStatus = "QUEUED"
// Make HTTP request to execute method
this.$http.post("/api/Method/" + methID + "/Execute")
.then(response => {
}, error =>
console.log("Couldn't execute Test: " + JSON.stringify(error))
)
},
registerExecutorSocket() {
const socket = new WebSocket("ws://localhost:4567/api/Executor/")
socket.onmessage = msg => {
const {methodID, report, executionStatus} = JSON.parse(msg.data)
for(let i = 0; i < this.project.classes.length; ++i) {
const klass = this.project.classes[i]
for(let j = 0; j < klass.methods.length; ++j) {
const meth = klass.methods[j]
if(meth.id === methodID) {
if(report)
this.project.classes[i].methods[j].latestReport = report
if(executionStatus)
this.project.classes[i].methods[j].executionStatus = executionStatus
return
}
}
}
}
},
prettyName: function(name) {
const split = name.split(".")
return split[split.length-1]
}
},
components: {
"ClassView": ClassView
}
}
</script>
<style scoped>
</style>
If your intention is for the computed property to update when project.classes.someSubProperty changes, that sub-property has to exist when the computed property is defined. Vue cannot detect property addition or deletion, only changes to existing properties.
This has bitten me when using a Vuex store with en empty state object. My subsequent changes to the state would not result in computed properties that depend on it being re-evaluated. Adding explicit keys with null values to the Veux state solved that problem.
I'm not sure whether explicit keys are feasible in your case but it might help explain why the computed property goes stale.
Vue reactiviy docs, for more info:
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
I've ran into similar issue before and solved it by using a regular method instead of computed property. Just move everything into a method and return your ret.
Official docs.
I had this issue when the value was undefined, then computed cannot detect it changing. I fixed it by giving it an empty initial value.
according to the Vue documentation
I have a workaround for this kind of situations I don't know if you like it. I place an integer property under data() (let's call it trigger) and every time the object that I used in computed property changes, it gets incremented by 1. So, this way, computed property updates every time the object changes.
Example:
export default {
data() {
return {
trigger: 0, // this will increment by 1 every time obj changes
obj: { x: 1 }, // the object used in computed property
};
},
computed: {
objComputed() {
// do anything with this.trigger. I'll log it to the console, just to be using it
console.log(this.trigger);
// thanks to this.trigger being used above, this line will work
return this.obj.y;
},
},
methods: {
updateObj() {
this.trigger += 1;
this.obj.y = true;
},
},
};
here's working a link
If you add console.log before returning, you may be able to see computed value in filteredClasses.
But DOM will not updated for some reason.
Then you need to force to re-render DOM.
The best way to re-render is just adding key as computed value like below.
<div
:key="JSON.stringify(filteredClasses)"
v-for="(klass, classIndex) in filteredClasses"
>
<ClassView
:key="classIndex"
:klass-raw="klass"
/>
</div>
Caution:
Don’t use non-primitive values like objects and arrays as keys. Use string or numeric values instead.
That is why I converted array filteredClasses to string. (There can be other array->string convert methods)
And I also want to say that "It is recommended to provide a key attribute with v-for whenever possible".
You need to assign a unique key value to the list items in the v-for. Like so..
<ClassView :klass-raw="klass" :key="klass.id"/>
Otherwise, Vue doesn't know which items to udpate. Explanation here https://v2.vuejs.org/v2/guide/list.html#key
I have the same problem because the object is not reactivity cause I change the array by this way: arrayA[0] = value. The arrayA changed but the computed value that calculate from arrayA not trigger. Instead of assign value to the arrayA[0], you need to use $set for example.
You can dive deeper by reading the link below
https://v2.vuejs.org/v2/guide/reactivity.html
I also use some trick like adding a cache = false in computed
compouted: {
data1: {
get: () => {
return data.arrayA[0]
},
cache: false
}
}
For anybody else being stuck with this on Vue3, I just resolved it and was able to get rid of all the this.$forceUpdate()-s that I had needed before by wrapping the values I returned from the setup() function [and needed to be reactive] in a reference using the provided ref() function like this:
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'CardDisplay',
props: {
items: {
type: Array,
default: () => []
},
itemComponent: Object,
maxItemWidth: { type: Number, default: 200 },
itemRatio: { type: Number, default: 1.25 },
gapSize: { type: Number, default: 50 },
maxYCount: { type: Number, default: Infinity }
},
setup () {
return {
containerSize: ref({ width: 0, height: 0 }),
count: ref({ x: 0, y: 0 }),
scale: ref(0),
prevScrollTimestamp: 0,
scroll: ref(0),
isTouched: ref(false),
touchStartX: ref(0),
touchCurrentX: ref(0)
}
},
computed: {
touchDeltaX (): number {
return this.touchCurrentX - this.touchStartX
}
},
...
}
After doing this every change to a wrapped value is reflected immediately!
If you are adding properties to your returned object after vue has registered the object for reactivity then it won't know to listen to those new properties when they change. Here's a similar problem:
let classes = [
{
my_prop: 'hello'
},
{
my_prop: 'hello again'
},
]
If I load up this array into my vue instance, vue will add those properties to its reactivity system and be able to listen to them for changes. However, if I add new properties from within my computed function:
computed: {
computed_classes: {
classes.map( entry => entry.new_prop = some_value )
}
}
Any changes to new_prop won't cause vue to recompute the property, as we never actually added classes.new_prop to vues reactivity system.
To answer your question, you'll need to construct your objects with all reactive properties present before passing them to vue - even if they are simply null. Anyone struggling with vues reactivity system really should read this link: https://v2.vuejs.org/v2/guide/reactivity.html

How to test computed properties in Vue.js? Can't mock "data"

I wonder how to test computed properties in Vue.js's unit tests.
I have create a new project via vue-cli (webpack based).
For example here are my Component:
<script>
export default {
data () {
return {
source: []
}
},
methods: {
removeDuplicates (arr) {
return [...new Set(arr)]
}
},
computed: {
types () {
return this.removeDuplicates(this.source))
}
}
}
</script>
I've tried to test it like this
it('should remove duplicates from array', () => {
const arr = [1, 2, 1, 2, 3]
const result = FiltersList.computed.types()
const expectedLength = 3
expect(result).to.have.length(expectedLength)
})
QUESTION (two problems):
this.source is undefined. How to mock or set value to it? (FiltersList.data is a function);
Perhaps I don't wan't to call removeDuplicates method, but how to mock(stub) this call?
Okay. I've found a dumb solution. Dumb but works.
You have been warned =)
The idea: To use .call({}) to replace this inside that calls:
it('should remove duplicates from array', () => {
const mockSource = {
source: [1, 2, 1, 2, 3],
getUniq (arr) {
return FiltersList.methods.removeDuplicates(arr)
}
}
const result = FiltersList.computed.types.call(mockSource)
const expectedLength = 3
expect(result).to.have.length(expectedLength)
})
So basically you can specify your own this with any kind of data.
and call YourComponent.computed.foo.call(mockSource). Same for methods
Just change the variable from which depends computed property and expect it.
This is my work example for component computed prop:
import Vue from 'vue'
import Zoom from 'src/components/Zoom'
import $ from 'jquery'
/* eslint-disable no-unused-vars */
/**
* get template for wrapper Vue object make Vue with Zoom component and that template
* #param wrapperTemplate
* #returns {Vue$2}
*/
const makeWrapper = (wrapperTemplate = '<div><zoom ref="component"></zoom></div>') => {
return new Vue({
template: wrapperTemplate,
components: {Zoom}
})
}
const startWrapperWidth = 1093
const startWrapperHeight = 289
const startImageWidth = 1696
const startImageHeight = 949
const padding = 15
/**
* gets vueWrapper and return component from it
* #param vueWrapper
* #param useOffset
* #returns {'Zoom component'}
*/
const setSizesForComponent = (vueWrapper) => {
vueWrapper.$mount()
var cmp = vueWrapper.$refs.component
var $elWrapper = $(cmp.$el)
var $elImage = $elWrapper.find(cmp.selectors.image)
$elWrapper.width(startWrapperWidth)
$elWrapper.height(startWrapperHeight)
$elWrapper.css({padding: padding})
$elImage.width(startImageWidth)
$elImage.height(startImageHeight)
cmp.calculateSizesAndProportions()
return cmp
}
describe('onZoom method (run on mousemove)', () => {
sinon.spy(Zoom.methods, 'onZoom')
let vueWrapper = makeWrapper()
let cmp = setSizesForComponent(vueWrapper)
let e = document.createEvent('HTMLEvents')
e.initEvent('mousemove', true, true)
e.pageX = 150
e.pageY = 250
let callsOnZoomBeforeMousemove = Zoom.methods.onZoom.callCount
cmp.$el.dispatchEvent(e)
describe('left and top computed props', () => {
it('left', () => {
expect(cmp.left).is.equal(-74)
})
it('top', () => {
expect(cmp.top).is.equal(-536)
})
})
})

Categories

Resources