How to mutate VueJS prop? - javascript

Hi i'm having trouble understanding how to mutate a prop value in vue js. I'm using vue-chartjs to dynamically rerender a chart using chartjs. The behaviour works but I get a console message warning when I fire off the updateValues() function.
Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders. Instead, use a
data or computed property based on the prop's value. Prop being
mutated: "myData"
How do I properly mutate the prop?
// Parent Component
<bar-graph :myData="dataCollection" :height="250"></bar-graph>
data () {
return {
dataCollection: {
labels: [2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017],
datasets: [
{
label: 'Sample Lables',
backgroundColor: 'red',
data: [5000, 5000, 5000, 5000, 5500, 5500, 10000, 5500, 5500]
}
]
}
}
},
methods: {
updateValues () {
this.dataCollection = {
labels: [5000, 5000, 5000, 5000, 5500, 5500, 10000, 5500, 5500],
datasets: [
{
label: 'Sample Lables',
backgroundColor: 'red',
data: [5000, 5000, 5000, 5000, 5500, 5500, 10000, 5500, 5500]
}
]
}
}
}
//Child component bar graph
import { Bar } from 'vue-chartjs'
export default Bar.extend({
props: ['myData'],
mounted () {
this.renderChart(this.myData, {responsive: true, maintainAspectRatio: false})
},
watch: {
myData: function () {
console.log('destroy')
this._chart.destroy()
this.renderChart(this.myData, {responsive: true, maintainAspectRatio: false})
}
}
})

There is no way to "properly" mutate a prop, because it is a input to a component.
I recommend importing the data passed via the prop to the component's state and then using accordingly. By using this local copy, you avoid mutating the prop and getting that warning.
export default Bar.extend({
props: ['myData'],
data() {
return {
passedData: null
}
}
mounted() {
// Import data from prop into component's state
this.passedData == this.myData;
// Use as desired
this.renderChart(this.myData, {
responsive: true,
maintainAspectRatio: false
})
},
watch: {
myData: function() {
console.log('destroy')
this._chart.destroy()
this.renderChart(this.myData, {
responsive: true,
maintainAspectRatio: false
})
}
}
})

A comment / addition to #TheCascadian's answer: If myData is an Object, then this.passedData would be a reference to the same object, so you'll still get that warning. You might consider using cloneDeep from lodash to have a real inner copy of the property and use it internally accordingly.

Related

React draft wysiwyg default font size

Could I ask you how to change default font size in react draft wysiwyg https://github.com/jpuri/react-draft-wysiwyg#readme? Class defining toolbar:
export const toolbar = {
options: ['inline', 'textAlign', 'list', 'link', 'fontSize', 'colorPicker', 'emoji'],
inline: {
inDropdown: false,
className: undefined,
component: undefined,
dropdownClassName: undefined,
options: ['bold', 'italic'],
},
list: {
inDropdown: false,
className: undefined,
component: undefined,
dropdownClassName: undefined,
options: ['unordered'],
},
textAlign: {
inDropdown: false,
className: undefined,
component: undefined,
dropdownClassName: undefined,
options: ['left', 'center', 'right']
},
link: {
inDropdown: false,
className: undefined,
component: undefined,
popupClassName: undefined,
dropdownClassName: undefined,
showOpenOptionOnHover: true,
defaultTargetOption: '_self',
options: ['link'],
linkCallback: undefined
},
fontSize: {
options: [8, 9, 10, 11, 12, 14, 16, 18, 24, 30, 36, 48, 60, 72, 96],
className: undefined,
component: undefined,
dropdownClassName: undefined,
},
colorPicker: {
className: undefined,
component: undefined,
popupClassName: undefined,
},
emoji: {
inDropdown: true,
className: undefined,
component: undefined,
popupClassName: undefined,
},
}
Font size 14 is defautl now. I don't know why. I searched for 14 in all the sourcecode and I didn't found it anyhere. When option 14 is not present in list no font size is defaultly selected. Wanted is to preselect option font size = 24. Thanks for reply.
I had same question and struggled with it even a woking day!
It's a pity that there is no oficial solution in documentation...
Found 2 solutions:
Simple (but not flexible):
just add this css code
.DraftEditor-root {
font-size: 24px;
}
This would apply size=24px to all the react-draft-wysiwyg items on the page/
Found this in library source code:
https://github.com/jpuri/react-draft-wysiwyg/blob/f59ee8419cdbd45aab3bdfdf1995f112b09bbb6a/src/controls/FontSize/Component/index.js#L30
Complex, but more flexible:
Firstli, import util functions (react-draft-wysiwyg uses this library itself)
import {
toggleCustomInlineStyle, getSelectionCustomInlineStyle,
} from 'draftjs-utils';
Secondly, on each render(!?)
you should execute:
const fontSize = getSelectionCustomInlineStyle(editorState, ['FONTSIZE',]).FONTSIZE
if (!fontSize) {
setEditorState(toggleCustomInlineStyle(editorState, 'fontSize', 24))
}
Why on each render and not on creating EditorState?
I don't know.
But this custom style is reset to empty (so - to default) when focus editor, so I have to force it each time.
I hope first solution would be enought for me and for you, because second looks like workaround and bad practice!

Change element prop in runtime

I have a chart component, and my job is to make a button to change it's type (eg. columns to pie), but i don't know how to change it on a button click event. Here's the structure of the component (the idea is to change the :series-defaults-type when the button with ChangeType id is pressed)
<template>
<div style="width: 100%;overflow: overlay;border-radius: 20px;">
<button id="changeType" #click="changeType()">Change</button>
<chart-more-option :kpiName="'EquipmentRetirementForecast'" v-if="showMoreOptions"/>
<chart :title-text="'Equipment Retirement Forecast'"
:title-color="'#FFF'"
:title-font="'openSans'"
:chart-area-background="'#1B1534'"
:legend-visible="false"
:series-defaults-type= "'column'"
:series="series"
:category-axis="categoryAxis"
:axis-defaults-color="'#FFF'"
:axis-defaults-labels-rotation-angle="'30'"
:value-axis="valueAxis"
:tooltip="tooltip"
:theme="'sass'"
:zoomable-mousewheel="true">
</chart>
</div>
</template>
<script>
import { Chart } from '#progress/kendo-charts-vue-wrapper';
import ChartMoreOption from '../ChartMoreOption';
export default {
name: 'EquipmentRetirementForecast',
components: {
'chart': Chart,
ChartMoreOption
},
props: {
fetchData: {
type: Boolean,
default: false
},
showMoreOptions: {
type: Boolean,
default: true,
},
},
watch: {
labelAlign(){
var c = this.$refs.chart
c.updateWidget();
}
},
computed:{
requestBody(){
return this.$store.getters['usersession/getTopologyRequestBody']
},
series(){
return this.$store.getters['riskmanagement/getRetirementForecastSeries']
},
categoryAxis(){
return this.$store.getters['riskmanagement/getRetirementForecastCategoryAxis']
},
},
data: function() {
return {
valueAxis: [{
line: {
visible: false
},
minorGridLines: {
visible: true
},
labels: {
rotation: "auto"
}
}],
tooltip: {
visible: true,
template: "#= series.name #: #= value #",
},
}
},
mounted(){
if(this.fetchData){
this.$store.dispatch("riskmanagement/FetchRetirementForecastData",this.requestBody).then(()=>{
});
}
},
methods: {
changeType(){
//code goes here
}
}
}
</script>
<style src="../style-dashboard.scss" lang="scss" scoped />
This is the chart i need to change:
Changing the :series-defaults-type to pie by hand, it works, but i need to make that change in a button click, as follows:
Add a data property and give it the default of 'column', name it for example chartType. Then inside the changeType() you add this.chartType = 'pie'. And change :series-defaults-type= "'column'" to :series-defaults-type= "chartType".
Also remember to NOT use : for attribute values that are hardcoded. So :chart-area-background="'#1B1534'" should be chart-area-background="#1B1534".

Dynamic lookup from React's state in material-table

I'm using the material-table component, which I fill with dynamic data coming from Redux (an array of objects), but then I do other things with that data inside my component's state. To create column dropdown filters, there's an element inside each column's array of options, lookup, that receives an object and creates the dropdown based on it's values.
I am extracting some items from my data and putting them inside an element in my component's state. This is an object, the same kind that lookup receives. The thing is that the component shows an empty dropdown, as if the object was empty, but it's not. I'm logging it in into the console and the object is filled with the data I need.
I initially thought it was a render problem, that the object is empty at the beggining, and then it's filled with data, but the component renders every time.(Yeah, React is reactive).
This is only the code needed to help me solve this problem:
Table component
import React, { Component } from "react";
import MaterialTable from "material-table";
class CustomTable extends Component {
state = {
column1: "",
column2: "",
column3: "",
column1FilterList: {}
columns: [
{
title: "Column1",
field: "column1",
editable: "onAdd",
filtering: true,
lookup: { ...this.column1FilterList }
},
{
title: "Column2",
field: "column2",
editable: "onAdd",
filtering: true,
},
{
title: "Column3",
field: "column3",
editable: "onAdd",
filtering: true
}
]
};
componentDidMount() {
this.props.fetchValues()
this.props.fetchApplications()
this.filterColumn1ExistingKeys()
}
filterColumn1ExistingKeys = () => {
return this.props.elements.map(element => {
return this.setState(prevState => ({
column1FilterList: {
...prevState.column1FilterList,
[element.name]: element.name
}
}))
})
}
render() {
return (
<div>
<MaterialTable
options={{
search: false,
actionsColumnIndex: 4,
filtering: true
}}
title="Search by"
columns={this.state.columns}
data={this.state.data}
/>
</div>
);
}
}
export default CustomTable;
The problem is how you save that data. You create a new object in the constructor with { ...this.column1FilterList }. This will create a new object which will act as the lookup object, which is filled with the initial data of column1FilterList (empty). Updating the column1FilterList later does not change that lookup object, because it is disconnected (new object). You have to update the lookup within the columns as well like this:
const filterColumn1ExistingKeys = () => {
const column1FilterList = this.state.column1FilterList;
this.props.elements.forEach(element => column1FilterList[element.name] = element.name)
this.setState({
column1FilterList,
columns: [{
title: "Column1",
field: "column1",
editable: "onAdd",
filtering: true,
lookup: { ...column1FilterList }
},
{
title: "Column2",
field: "column2",
editable: "onAdd",
filtering: true,
},
{
title: "Column3",
field: "column3",
editable: "onAdd",
filtering: true
}
]
})
}
Hope this helps. Let me know, if that works for you. If you have any questions, let me know. Happy coding.

Vue property definition warning even though it is defined on the instance

Edit - I have setup a repo on github with the erraneous code here if anyone wants to pull this down and see the error for themselves: https://github.com/andrewjrhill/what-the-instance-grid. You can run npm run serve to kick off the webserver.
I am running into an issue where my Vue is throwing the following errors:
[Vue warn]: Property or method "columns" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
[Vue warn]: Property or method "items" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
This is a pretty common issue with Vue apps and is usually the result of a property not being defined on a Vue data object. Unfortunatley in this case I have indeed added columns and itemsto the new Vue call. Any ideas why I am getting this error? It looks like data isn't available at all to the template.
This project was generated by the latest Vue-CLI and is using the runtimeCompiler: true flag in a vue.config.js file if that makes any difference.
The .vue file in question:
<template>
<div id="vueapp" class="vue-app">
<Grid :columns="columns" :data-items="items" :style="{ height: '280px' }"></Grid>
</div>
</template>
<script>
import Vue from "vue";
import { Grid } from "#progress/kendo-vue-grid";
Vue.component("Grid", Grid);
new Vue({
el: "#vueapp",
data: function() {
return {
items: [],
columns: [
{ field: "ProductID" },
{ field: "ProductName", title: "Product Name" },
{ field: "UnitPrice", title: "Unit Price" }
]
};
},
methods: {
createRandomData(count) {
const productNames = [
"Chai",
"Chang",
"Syrup",
"Apple",
"Orange",
"Banana",
"Lemon",
"Pineapple",
"Tea",
"Milk"
];
const unitPrices = [12.5, 10.1, 5.3, 7, 22.53, 16.22, 20, 50, 100, 120];
return Array(count)
.fill({})
.map((_, idx) => ({
ProductID: idx + 1,
ProductName:
productNames[Math.floor(Math.random() * productNames.length)],
UnitPrice: unitPrices[Math.floor(Math.random() * unitPrices.length)]
}));
}
},
mounted() {
this.items = this.createRandomData(50);
}
});
export default {
name: "App",
components: {
Grid
}
};
</script>
Don't reinstantiate Vue inside the App.vue component.
Fix like this (files from your repo):
main.js:
import App from './App.vue'
import Vue from 'vue'
import { Grid } from "#progress/kendo-vue-grid";
Vue.component("Grid", Grid);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#vueapp')
App.vue:
<template>
<div id="vueapp" class="vue-app">
<Grid :columns="columns" :data-items="items" :style="{ height: '280px' }"></Grid>
</div>
</template>
<script>
export default {
name: "App",
data: function() {
return {
items: [],
columns: [
{ field: "ProductID" },
{ field: "ProductName", title: "Product Name" },
{ field: "UnitPrice", title: "Unit Price" }
]
};
},
methods: {
createRandomData(count) {
// your code
}
},
mounted() {
this.items = this.createRandomData(50);
}
};
</script>

Polymer JS - Parametric neon-animation config

I have a component - floating digit that animates from bottom to top.
And this code is working perfectly fine:
<dom-module id="floating-digit">
<style>
span.increment {
display: none;
font-size: 12px;
font-weight: normal;
position: absolute;
right: -10px;
top: 1.25em;
}
</style>
<template>
<span id="floater" class="increment">+[[digit]]</span>
</template>
</dom-module>
<script>
Polymer({
is: "floating-digit",
behaviors: [Polymer.NeonAnimationRunnerBehavior],
properties: {
digit: {
type: String,
value: "",
observer: '_animateDigit'
},
transformFrom: {
type: String,
value: "translateY(0)"
},
transformTo: {
type: String,
value: "translateY(-100%)"
},
animationConfig: {
value: function () {
return {
'animate': [
{
name: 'fade-in-animation',
node: this.$.floater,
timing: {duration: 100}
},
{
name: 'transform-animation',
node: this.$.floater,
transformFrom: "translateY(0)",
transformTo: "translateY(-100%)",
timing: {duration: 1500}
},
{
name: 'fade-out-animation',
node: this.$.floater,
timing: {duration: 2000, delay: 2000}
},
]
}
}
}
},
listeners: {
'neon-animation-finish': '_onNeonAnimationFinish'
},
_animateDigit: function _animateDigit() {
this.$.floater.style.display = "inline";
this.playAnimation('animate');
},
_onNeonAnimationFinish: function _onNeonAnimationFinish() {
this.$.floater.style.display = 'none';
},
})
</script>
But there are several places on my app where I want to use this component with different transformFrom and transformTo values.
That is why I added to my floating-digit properties transformTo and transformFrom, so I can parametrize them when I want those values to be different than default:
<floating-digit id="floating-0" transform-from="translateY(-20%)" transform-to="translateY(-80%)" digit="[[someNumber]]"></floating-digit>
I have changed animationConfig this way (only changed excerpt):
{
name: 'transform-animation',
node: this.$.floater,
transformFrom: this.transformFrom,
transformTo: this.transformTo,
timing: {duration: 1500}
},
But it does not work. This function seems not to have access to all those properties defined in element.
console.log(this) inside function returning animationConfig object value properly identifies this as desired instance of <floating-digit>. Unfortunately, without properties that can be used for parametrizing configuration of animations. They remain undefined.
Any of you possibly have an idea how to access those parameters?
If anyone is interested I have found solution. Unfortunately this solution does not use Polymer to grab those parameters.
I am using native web API to do this:
My animationConfig looks that way:
animationConfig: {
value: function () {
let transformFrom = this.attributes.getNamedItem('transform-from') ? this.attributes.getNamedItem('transform-from').textContent : "translateY(0)";
let transformTo = this.attributes.getNamedItem('transform-to') ? this.attributes.getNamedItem('transform-to').textContent : "translateY(-100%)";
return {
'animate': [
{
name: 'fade-in-animation',
node: this.$.floater,
timing: {duration: 100}
},
{
name: 'transform-animation',
node: this.$.floater,
transformFrom: transformFrom,
transformTo: transformTo,
timing: {duration: 1500}
},
{
name: 'fade-out-animation',
node: this.$.floater,
timing: {duration: 2000, delay: 2000}
},
]
}
}
}
And I am calling this same way as before:
<floating-digit id="floating-0" transform-from="translateY(-20%)" transform-to="translateY(-70%)" digit="5"></floating-digit>
If anyone have idea why this does not work properly, you are more than invited to share your solution.

Categories

Resources