I'm importing an array into a module, and adding and removing items from that array. when I give a push, it adds the item to the array globally, so much so that if I use that same array in another module, it will include this item that I pushed. but when I try to filter, with that same array getting itself with the filter, it only removes in that specific module. How can I make it modify globally?
let { ignore } = require('../utils/handleIgnore');
const questions = require('./quesiton');
const AgendarCollector = async (client, message) => {
ignore.push(message.from);
let counter = 0;
const filter = (m) => m.from === message.from;
const collector = client.createMessageCollector(message.from, filter, {
max: 4,
time: 1000 * 60,
});
await client.sendText(message.from, questions[counter++]);
collector.on('start', () => {});
await collector.on('collect', async (m) => {
if (m) {
if (counter < questions.length) {
await client.sendText(message.from, questions[counter++]);
}
}
});
await collector.on('end', async (all) => {
ignore = ignore.filter((ignored) => ignored !== message.from);
console.log(ignore);
const finished = [];
if (all.size < questions) {
console.log('não terminado');
}
await all.forEach((one) => finished.push(` ${one.content}`));
await client.sendText(message.from, `${finished}.\nConfirma?`);
});
};
module.exports = AgendarCollector;
see, in this code, import the ignore array and i push an item to then when the code starts and remove when its end.
but the item continues when I check that same array in another module.
I tried to change this array ignore by using functions inside this module but still not working
let ignore = [];
const addIgnore = (message) => {
ignore.push(message.from);
};
const removeIgnore = (message) => {
ignore = ignore.filter((ignored) => ignored !== message.from);
console.log(ignore);
};
console.log(ignore);
module.exports = { ignore, addIgnore, removeIgnore };
You are using the variables for import and export and hence cought up with issues.
Instead, make use of getters.
Write a function which will return the array of ignore. something like this:
const getIgnoredList = () => {
return ignore;
};
and in your first code, import getIgnoredList and replace ignore with getIgnoredList()
Explanation :
Whenever we import the variables only the value at that particular time will be imported and there will not be any data binding. Hence there won't be any change in the data even though you think you are updating the actual data.
When you use require(...) statement it's executed only once. Hence when you try to access the property it gives the same value everytime.
Instead you should use getters
let data = {
ignore : [],
get getIgnore() {
return this.ignore
}
}
module.export = {
getIgnore: data.getIgnore,
}
Then wherever you want to access ignore do
var {getIgnore}= require('FILE_NAME')
Now: console.log(getIgnore) will invoke the getter and give you current value of ignore
Using getters will allow you to access particular variables from other modules but if you want to make changes in value of those variables from other module you have to use setter.
More about getters here
More about setters here
Related
I have two states defined like so:
const [productProperties, setProductProperties] = useState<
PropertyGroup[] | null
>(null);
const [originalProductProperties, setOriginalProductProperties] = useState<
PropertyGroup[] | null
>(null);
The first one is supposed to be updated through user input and the second one is used later for a comparison so that only the PropertyGroup's that have changed values will be submitted via API to be updated.
I have done this a thousand times before, but for some reason when I change the name value for a PropertyGroup and update the state for 'productProperties' like so:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i].name = {
...update[i].name,
[selectedLocale]: e.currentTarget.value,
};
setProductProperties([...update]);
}
}
The state of originalProductProperties also updates. Why? setOriginalProductProperties is never called here, I am also not mutating any state directly and I use the spread operator to be sure to create new references. I am lost.
Preface: It sounds like the two arrays are sharing the same objects. That's fine provided you handle updates correctly.
Although you're copying the array, you're modifying the object in the array directly. That's breaking the main rule of state: Do Not Modify State Directly
Instead, make a copy of the object as well:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update); // (No need to *re*copy the array here, you've already done it at the top of the function)
}
}
Or, since you have that i !== -1 check there, we could copy the array later so we don't copy it if we don't find the group matching itemId:
(e, itemId) => {
const i = productProperties.findIndex((group) => group.id === itemId);
if (i !== -1) {
const update = [...productProperties];
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update);
}
}
FWIW, in cases where you know there will be a match, map is good for this (but probably not in this case, since you seem to indicate the group may not be there):
(e, itemId) => {
const update = productProperties.map((group) => {
if (group.id === itemId) {
// It's the one we want, create the replacement
group = {
...group,
[selectedLocale]: e.currentTarget.value,
};
}
return group;
});
setProductProperties(update);
}
Or sometimes you see it written with a conditional operator:
(e, itemId) => {
const update = productProperties.map((group) =>
group.id === itemId
? { // It's the one we want, create a replacement
...group,
[selectedLocale]: e.currentTarget.value,
}
: group
);
setProductProperties(update);
}
I'm working on a VueJS component that, among other things, can export data to .xlsx. For this, I'm using the json2xls library - so I need to pass to the json2xls() function an array of objects with identical keys (said keys will be column names)
This data I have to export is in an array of pretty deeply nested objects, though, so I need a function that will process that data to a form that will work with json2xls.
This is the method I'm using for that:
exportReport () {
const dataMap = []
this.reportPreview.forEach(elm => {
const auxObj = {}
auxObj.name = `${elm.client.first_name} ${elm.client.surname_1} ${elm.client.surname_2}`
elm.legal_files.forEach((e) => {
auxObj.legalfile = e.code
auxObj.actions = e.actions.count
dataMap.push(auxObj)
})
})
exportToXls(dataMap, `action-report-by-client-${this.options.start_date}-${this.options.end_date}.xlsx`)
}
If I do this, however, it appears that in cycles of elm.legal_files.forEach() the properties auxObj.legalfile and auxObj.actions are not overwritten, pushing several objects with identical values to dataMap
Why is this happening? What am I doing wrong? I'm hacking my way around this copying auxObj after "overwriting" the legalfile and actions properties and pushing the copy. This hack works, but I wonder what causes the first behavior and if there's a cleaner way around it.
exportReport () {
const dataMap = []
this.reportPreview.forEach(elm => {
const auxObj = {}
auxObj.name = `${elm.client.first_name} ${elm.client.surname_1} ${elm.client.surname_2}`
elm.legal_files.forEach((e) => {
auxObj.legalfile = e.code
auxObj.actions = e.actions.count
/*
If I just push auxObj to dataMap, the object is pushed with the same properties every time.
Copying auxObj and pushing the copy is a hack around this problem, but there may be a cleaner solution.
*/
const objCopy = { ...auxObj }
dataMap.push(objCopy)
})
})
exportToXls(dataMap, `action-report-by-client-${this.options.start_date}-${this.options.end_date}.xlsx`)
}
You pushed the same object every time.
exportReport() {
const dataMap = []
this.reportPreview.forEach(elm => {
const name = `${elm.client.first_name} ${elm.client.surname_1} ${elm.client.surname_2}`
elm.legal_files.forEach((e) => {
const auxObj = {} // Create a new object here
auxObj.name = name
auxObj.legalfile = e.code
auxObj.actions = e.actions.count
dataMap.push(auxObj) // Push it into the array
})
})
exportToXls(dataMap, `action-report-by-client-${this.options.start_date}-${this.options.end_date}.xlsx`)
}
Is there a way where I can use for loops and if statements without breaking the hook rule? To elaborate, I am currently trying to compare two lists (allData and currentSelection) and if there are similarities, I will add them to another list (favData). However, I am constantly either having visibility issues or errors. If I can get some help, I would much appreciate it!
const [favData, setFavData] = useState([]);
useEffect(() => {
getFilterFavMeal();
}, []);
function getFilterFavMeal() {
allData.forEach((mealList) => {
currentSelection.forEach((mealList2) => {
if (mealList["menu_item"]["menu_item_id"] === mealList2.value) {
// with push, I have visibility issues
// favData.push(mealList);
setFavData(mealList);
}
});
});
setFavData(favData);
}
The set function that useState returns updates the state and schedules a re-render of the component so that the UI can update. It doesn't make sense to call the set function many times in one render.
You also don't want to mutate React state by using functions like push.
Since it looks like favData is deterministic, you can simply remove it from the component state and calculate it in the render loop.
const favData = allData.filter(a => currentSelection.some(c => c.value === a.menu_item.menu_item_id));
Answering your original question, of course you can use loops. As long as you don't mutate the existing state object. And don't set the state more than once per render.
const FF = () => {
const [list, setList] = useState([]);
const addStuffToList = () => {
const tail = Array.from(new Array(3)).map((_e, i) => i);
// Build a new array object and use that when setting state
setList([...list, ...tail]);
}
const forLoop = () => {
const tail = [];
for (let i = 0; i < 4; i++) {
tail.push(i);
}
// Same thing
setList([...list, ...tail]);
}
return ...
};
The following image represents an object with two ui controls that are stored as this.state.controls
Initially the statesValue values are set via data that is received prior to componentDidMount and all is good. However updates to the each of the controls statesValues are sent via an event , which is handled with the following function
const handleValueStateChange = event => {
let controls = Object.entries(this.state.controls);
for (let cont of controls) {
let states = cont[1].states;
if (states) {
let state = Object.entries(states);
for (let [stateId, contUuid] of state) {
if (contUuid === event.uuid) {
cont[1].statesValue[stateId] = event.value;
}
}
}
}
};
which updates the values happily, however bearing in mind the updated values that change are a subset of this.state.controls, I have no idea how to use this.setState to update that that has been changed.
thanks in advance for any pointers
Instead of using Object.entries try destructuring to keep the reference to the objects.
And have a look at lodash. There are some nice helper functions to iterate over objects like mapValues and mapKeys. So you can keep the object structure and just replace the certain part. Afterwards update the whole state-object with the new one.
const handleValueStateChange = event => {
let {controls} = this.state;
controls = _.mapValues(controls, (cont) => {
const states = cont[1].states;
if (states) {
_.mapValues(states, (contUuid,stateId) => {
if (contUuid === event.uuid) {
cont[1].statesValue[stateId] = event.value;
}
});
}
return cont;
});
this.setState({controls});
};
Code is not tested but it should work like this.
Problem is you're updating an object which you've changed from it's original structure (by using Object.entries). You can still iterate in the same way however you'll need to update an object that maintains the original structure. Try this:
Make a copy of the controls object. Update that object. Replace it in state.
const handleValueStateChange = event => {
// copy controls object
const { controls } = this.state;
let _controls = Object.entries(controls);
for (let cont of _controls) {
let states = cont[1].states;
if (states) {
let state = Object.entries(states);
for (let [stateId, contUuid] of state) {
if (contUuid === event.uuid) {
// update controls object
controls[cont[0]].statesValue[stateId] = event.value;
}
}
}
}
}
// replace object in state
this.setState({controls});
};
I've come across a weird issue where a new variable is being created in local scope even if it is defined outside,
from the below code
after I call buildMeta() and check the contents of "data", it is always empty
implying that it's not being modified at all, even if I've specifically targeted "that.data" where that refers to the class' object.
I'd appreciate if anyone would point out what I am doing wrong.
class meta {
constructor(files) {
if(!files) throw Error("files not specified");
this.data = {};
this.ls = files;
}
buildMeta() {
var that = this;
for(let i = 0; i < that.ls.length; i++) {
mm.parseFile(that.ls[i]).then(x => {
var info = x.common;
that.data[info.artist] = "test";
}).catch((x) => {
console.log(x);
});
}
}
}
const mm = new meta(indexer); // indexer is an array of file paths
mm.buildMeta();
console.log(mm.data);
You're mixing sync with async code here.
The for loop won't wait for the parseFile promises to resolve.
You could use Promise.all to fill in the data when the files are parsed.
// Class names should be written using a capital letter
class Meta {
...
buildMeta() {
// You don't need this assignment since you're using arrow functions
// var that = this;
const promises = this.ls.map(filePath => mm.parseFile(filePath));
return Promise.all(promises).then(resolvedPromises => {
resolvedPromises.map(({ parsedFile }) => {
this.data[parsedFile.common.artist] = "test";
});
return this.data;
}).catch(console.error);
}
...
const mm = new meta(indexer); // indexer is an array of file paths
mm.buildMeta().then(data => {console.log(data)});
Hope this helps.
You are logging mm.data before parseFile has finished. Your code implies that it returns a promise, so your insertion into that.data will happen after your console.log(mm.data) executes.
You need to return a promise from buildMeta, so that you can do...
const mm = new meta(indexer);
mm.buildMeta().then(() => {
console.log(mm.data);
})
Here's a buildMeta that should do what you need. This returns a promise that waits for all of the parseFile invocations to do their work and update this.data...
buildMeta() {
return Promise.all(this.ls.map(f => mm.parseFile(f).then(x => {
var info = x.common;
this.data[info.artist] = "test";
})))
}