ESlint Error when using map - workaround? - javascript

What is the workaround to update the dataLine when using data.Items.map()
I am getting eslint error:
Assignment to property of function parameter 'dataLine'
You can see I am deleting Other property and modifying dataLine.Config
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
console.log(JSON.stringify(newItems, null, 2));

About eslint, I think it's a missing piece, because if you write your function in an equivalent way:
data.Items.map((dataLine) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
you won't receive any warning. Maybe it's the case of open an issue there.
You could pass {props : true}, like GProst said, but this will enforce you to not make the assignment of any property of the parameter, which is a good thing, for example:
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = { // not allowed with props : true
Size: "L"
};
delete dataLine.Other; // not allowed with props : true
}
return dataLine;
});
Why eslint have such a rule?
You are modifying the properties of data.Items, this will cause side effects on the external environment of the callback function on map. In some cases this will put you in bad situation, like not knowing which piece of code removed some property.
A suggestion about how you can deal with this safely is return an entire new object to make your data.Items immutable in your case:
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
const dataLineCopy = JSON.parse(JSON.stringify(dataLine))
if (data.Type == "API") {
dataLineCopy.Config = {
Size: "L"
};
delete dataLineCopy.Other;
}
return dataLineCopy;
});
console.log(JSON.stringify(newItems, null, 2));

Edit no-param-reassign rule in eslint config, set option props to false:
"no-param-reassign": ["error", { "props": false }]

Related

Using .map method to update the object that I'm evaluating

I am executing the map method on an array selectedItem where selectedItem has a string property nodeType and an object property items :
items = { computers: 0, drives: 0, files: 0, folders: 0 }
selectedItem?.map(item => {
switch (item.nodeType) {
case 'FileType':
return (items.files += 1);
case 'FolderType':
return (items.folders += 1);
case 'DriveType':
return (items.drives += 1);
case 'ComputerType':
return (items.computers += 1);
default:
return;
}
I know I should be able to replace the switch statement by using the built-in functionality of the map method (hasOwnProperty), but I've only seen examples where Object literals are used to match on in order to return the string, like this:
const itemTypes = {
file: 'file',
folder: 'folder',
drive: 'drive',
computer: 'computer'
}
However, upon a .map(expression) match I want to execute a simple incrementing logic to +=1 to a property within the object I'm evaluating.
I've looked at .filter() and .reduce() but of course don't apply since they return a different array. I'm sure it may have to deal with writing the correct function within the .map(expression) but not sure how to do that.
I've looked at these SO posts, but they either don't apply or don't get me all the way there. The second link is very close to what I'm looking for but not sure how to apply it to my code:
Update multiple objects using Javascript map() function
Update Map object property values
How would I do that using only map and not nesting a switch within the map function?
map() is used to create a new array. It is not useful when you want to update an object.
You should be using reduce to count up all the types
const selectedItem = [
{ nodeType: 'FolderType' },
{ nodeType: 'DriveType' },
{ nodeType: 'DriveType' },
{ nodeType: 'FolderType' },
{ nodeType: 'ComputerType' },
];
const types = {
FileType: 'files',
FolderType: 'folders',
DriveType: 'drives',
ComputerType: 'computers'
};
const items = {
computers: 0,
drives: 0,
files: 0,
folders: 0
};
selectedItem?.reduce((obj, item) => {
const type = types[item.nodeType];
if (type) obj[type]++;
return obj;
}, items);
console.log(items);
or forEach
const selectedItem = [
{ nodeType: 'FolderType' },
{ nodeType: 'DriveType' },
{ nodeType: 'DriveType' },
{ nodeType: 'FolderType' },
{ nodeType: 'ComputerType' },
];
const types = {
FileType: 'files',
FolderType: 'folders',
DriveType: 'drives',
ComputerType: 'computers'
};
const items = {
computers: 0,
drives: 0,
files: 0,
folders: 0
};
selectedItem?.forEach((item) => {
const type = types[item.nodeType];
if (type) items[type]++;
});
console.log(items);
You can use a map object to associate the node types to their items keys.
const itemsKeyByNodeType = {
FileType: 'files',
FolderType: 'folders',
DriveType: 'drives',
ComputerType: 'computers'
};
selectedItem?.forEach(item => {
if (itemsKeyByNodeType[item.nodeType]) {
items[itemsKeyByNodeType[item.nodeType]]++;
}
});

React setState of for deeply nested value

I’ve got a very deeply nested object in my React state. The aim is to change a value from a child node. The path to what node should be updated is already solved, and I use helper variables to access this path within my setState.
Anyway, I really struggle to do setState within this nested beast. I abstracted this problem in a codepen:
https://codesandbox.io/s/dazzling-villani-ddci9
In this example I want to change the child’s changed property of the child having the id def1234.
As mentioned the path is given: Fixed Path values: Members, skills and variable Path values: Unique Key 1 (coming from const currentGroupKey and both Array position in the data coming from const path
This is my state object:
constructor(props) {
super(props);
this.state = {
group:
{
"Unique Key 1": {
"Members": [
{
"name": "Jack",
"id": "1234",
"skills": [
{
"name": "programming",
"id": "13371234",
"changed": "2019-08-28T19:25:46+02:00"
},
{
"name": "writing",
"id": "abc1234",
"changed": "2019-08-28T19:25:46+02:00"
}
]
},
{
"name": "Black",
"id": "5678",
"skills": [
{
"name": "programming",
"id": "14771234",
"changed": "2019-08-28T19:25:46+02:00"
},
{
"name": "writing",
"id": "def1234",
"changed": "2019-08-28T19:25:46+02:00"
}
]
}
]
}
}
};
}
handleClick = () => {
const currentGroupKey = 'Unique Key 1';
const path = [1, 1];
// full path: [currentGroupKey, 'Members', path[0], 'skills', path[1]]
// result in: { name: "writing", id: "def1234", changed: "2019-08-28T19:25:46+02:00" }
// so far my approach (not working) also its objects only should be [] for arrays
this.setState(prevState => ({
group: {
...prevState.group,
[currentGroupKey]: {
...prevState.group[currentGroupKey],
Members: {
...prevState.group[currentGroupKey].Members,
[path[0]]: {
...prevState.group[currentGroupKey].Members[path[0]],
skills: {
...prevState.group[currentGroupKey].Members[path[0]].skills,
[path[1]]: {
...prevState.group[currentGroupKey].Members[path[0]].skills[
path[1]
],
changed: 'just now',
},
},
},
},
},
},
}));
};
render() {
return (
<div>
<p>{this.state.group}</p>
<button onClick={this.handleClick}>Change Time</button>
</div>
);
}
I would appreciate any help. I’m in struggle for 2 days already :/
Before using new dependencies and having to learn them you could write a helper function to deal with updating deeply nested values.
I use the following helper:
//helper to safely get properties
// get({hi},['hi','doesNotExist'],defaultValue)
const get = (object, path, defaultValue) => {
const recur = (object, path) => {
if (object === undefined) {
return defaultValue;
}
if (path.length === 0) {
return object;
}
return recur(object[path[0]], path.slice(1));
};
return recur(object, path);
};
//returns new state passing get(state,statePath) to modifier
const reduceStatePath = (
state,
statePath,
modifier
) => {
const recur = (result, path) => {
const key = path[0];
if (path.length === 0) {
return modifier(get(state, statePath));
}
return Array.isArray(result)
? result.map((item, index) =>
index === Number(key)
? recur(item, path.slice(1))
: item
)
: {
...result,
[key]: recur(result[key], path.slice(1)),
};
};
const newState = recur(state, statePath);
return get(state, statePath) === get(newState, statePath)
? state
: newState;
};
//use example
const state = {
one: [
{ two: 22 },
{
three: {
four: 22,
},
},
],
};
const newState = reduceStatePath(
state,
//pass state.one[1],three.four to modifier function
['one', 1, 'three', 'four'],
//gets state.one[1].three.four and sets it in the
//new state with the return value
i => i + 1 // add one to state.one[0].three.four
);
console.log('new state', newState.one[1].three.four);
console.log('old state', state.one[1].three.four);
console.log(
'other keys are same:',
state.one[0] === newState.one[0]
);
If you need to update a deeply nested property inside of your state, you could use something like the set function from lodash, for example:
import set from 'lodash/set'
// ...
handleClick = () => {
const currentGroupKey = 'Unique Key';
const path = [1, 1];
let nextState = {...this.state}
// as rightly pointed by #HMR in the comments,
// use an array instead of string interpolation
// for a safer approach
set(
nextState,
["group", currentGroupKey, "Members", path[0], "skills", path[1], "changed"],
"just now"
);
this.setState(nextState)
}
This does the trick, but since set mutates the original object, make sure to make a copy with the object spread technique.
Also, in your CodeSandbox example, you set the group property inside of your state to a string. Make sure you take that JSON string and construct a proper JavaScript object with it so that you can use it in your state.
constructor(props) {
super(props)
this.setState = { group: JSON.parse(myState) }
}
Here's a working example:
CodeSandbox

unexpected side effect vue

I am a newbie in vue.js. I have a problem with side effect in computed property. I'm not sure why i'm getting an unexpected side effect in computer property error with the code below. ESlint shows me this error in console. I found what does it mean, but I dont have any idea how to change. Any ideas?
export default {
name: "Repaid",
components: {
VueSlideBar
},
data() {
return {
response: {},
slider: {
lineHeight: 8,
value: 2000,
data: []
},
days: {},
monthCounts: [5],
currentMonthCount: 5,
isLoading: false,
errorMessage: "",
validationError: ""
};
},
computed: {
finalPrice() {
const index = this.monthCounts.indexOf(this.currentMonthCount);
this.days = Object.keys(this.response.prices)[index]; =>this is my side effect
const payment = this.response.prices[this.days]
[this.slider.value].schedule[0].amount;
}
},
I read that i should add slide() to my variable that.days, to mutate the original object. Or maybe I should carry them to the methodes.
EDIT
I carried all my calculations to methods and trigger a function in computed property.
Now it looks like this:
methods: {
showPeriod() {
const index = this.monthCounts.indexOf(this.currentMonthCount);
this.days = Object.keys(this.response.prices)[index];
const payment = this.response.prices[this.days][this.slider.value]
.schedule[0].amount;
return "R$ " + payment;
},
}
computed: {
finalPrice() {
if (!this.response.prices || this.monthCounts === []) {
return "";
}
return this.showPeriod();
}
},
It's coming from "eslint-plugin-vue" and the link for that rule is below,
https://eslint.vuejs.org/rules/no-side-effects-in-computed-properties.html
Either you override this rule in your eslint rules file or you can simply turn off the eslint for this specific line like below
this.days = Object.keys(this.response.prices)[index]; // eslint-disable-line
--
One more thing (Not related to your question) is that you need to return some value in computed.

JSON cannot ready property of null

I am calling API that is returning JSON array I am iterating over this array and map each value to new object and then push it new array.
Issue is when value from API is null i get
Cannot read property 'name' of null
but in my code i am handling null but still getting this error....
let comArr = [];
JSONData.issues.forEach(element => {
comArr.push({
Resolution: (element.fields.resolution.name === null) ? "some default value" : element.fields.resolution.name,
});
});
it seems that element.fields.resolution is null in same cases. Add a second if check on your statement, like:
if (element.fields.resolution === null){
comArr.push({ Resolution: "some default value" });
}
else{
comArr.push(Resolution: (element.fields.resolution.name === null) ? "some default value" : element.fields.resolution.name);
}
The issue is that your resolution object is null in some cases, so you should make a check to see if the object exists first. A cleaner solution would be to use map, since you are always pushing an object, rather than using forEach:
const JSONData = {
issues: [
{
fields: {}
},
{
fields: {
resolution: {
name: 'foo'
}
}
},
{
fields: {}
},
{
fields: {
resolution: {
name: 'bar'
}
}
}
]
}
const output = JSONData.issues.map(({fields: {resolution}}) => ({
Resolution: resolution && resolution.name ? resolution.name : "some default value"
}))
console.log(output)

Vuejs generate elements from object inside components

I'm trying to create a component that will render elements inside VueJs virtual dom with a Vuex state.
The problem is I get this error but I don't understand why and how to fix it
Avoid using observed data object as vnode data: {"class":"btn btn-default"}
Always create fresh vnode data objects in each render!
Inside my Vuex state I store and object where I define the elements properties
{
type: 'a',
config: {
class: 'btn btn-default',
},
nestedElements: [
{
type: 'span',
value: 'test',
},
{
type: 'i',
},
],
},
My components code look like
methods: {
iterateThroughObject(object, createElement, isNestedElement = false) {
const generatedElement = [],
nestedElements = [];
let parentElementConfig = {};
for (const entry of object) {
let nodeConfig = {};
if (typeof entry.config !== 'undefined' && isNestedElement) {
nodeConfig = entry.config;
} else if (typeof entry.config !== 'undefined') {
parentElementConfig = entry.config;
}
if (entry.nestedElements) {
nestedElements.push(this.iterateThroughObject(entry.nestedElements, createElement, true));
}
if (!isNestedElement) {
nodeConfig = parentElementConfig;
}
generatedElement.push(createElement(
entry.type,
nodeConfig === {} ? entry.value : nodeConfig,
nestedElements
));
}
if (isNestedElement) {
return generatedElement;
}
return createElement('ul', generatedElement);
},
},
render(createElement) {
const barToolsElements = this.$store.state.titleBar.barToolsElements;
if (barToolsElements) {
return this.iterateThroughObject(barToolsElements, createElement);
}
return false;
},
The error is produced when I try to pass inside my last generatedElement.push() definition.
Because entry.value is {"class":"btn btn-default"}.
I don't understand why it tell me to recreate a fresh Vnode object while this value is used only once.
Did I miss or misunderstand something?
Might be because you're passing references to objects in your store's state, which might lead inadvertently to their mutation. Try creating deep clones of these objects when you pass them around, like for example ..
nodeConfig = JSON.parse(JSON.stringify(entry.config));
parentElementConfig = JSON.parse(JSON.stringify(entry.config));
nodeConfig === {} ? JSON.parse(JSON.stringify(entry.value)) : nodeConfig,

Categories

Resources