I am creating a multistep form where a value (or multiples) of a form field determine whether a subsequent step is shown.
For example, the data I am receiving looks like:
{
cardConditional:
lessThan: 75
show: true
when: "fieldKeyHere"
...
}
This is basically telling me, if the when is lessThan 75 show the step. it can return greaterThan or eq as well which i've accounted for in the code below. My question is how can i take that information and construct a function to return true or false depending on that? I guess im stuck on how to string together that conditional with the string from getMathSymbol.
Here's what i have so far:
const checkStepConditional = (step) => {
const getMathSymbol = () => {
if (step.cardConditional.eq) return "===";
else if (step.cardConditional.lessThan) return "<";
else if (step.cardConditional.greaterThan) return ">";
};
if (step.cardConditional) {
const conditionalFieldKey = step.cardConditional.when;
return form.values[conditionalFieldKey] <-- stuck here
} else {
return true;
}
};
You can create an object keyed by the conditions which each implements the method necessary for the comparison, and use an every() on the conditions that exist in your cardConditional object to check of all conditions match.
const checkStepConditional = (form, step) => {
const checkIf = {
eq: (a, b) => a === b,
lessThan: (a, b) => a < b,
greaterThan: (a, b) => a > b,
}
if (step.cardConditional) {
const cardCond = step.cardConditional;
const field = form.values[cardCond.when];
const conditions = Object.keys(checkIf).filter(k => k in cardCond);
return conditions.every(condition => checkIf[condition](field, cardCond[condition]))
? cardCond.show
: !cardCond.show
}
};
const
card = { cardConditional: { lessThan: 75, show: true, when: "fieldKeyHere", } },
form = { values: { "fieldKeyHere": 60 } };
console.log(checkStepConditional(form, card));
// 60 < 75 ? show: true
const
card1 = { cardConditional: { lessThan: 75, greaterThan: 60, show: true, when: "fieldKeyHere", } },
form1 = { values: { "fieldKeyHere": 65 } };
console.log(checkStepConditional(form1, card1));
// 65 < 75 && 65 > 60 ? show: true
const
card2 = { cardConditional: { lessThan: 75, greaterThan: 60, show: true, when: "fieldKeyHere", } },
form2 = { values: { "fieldKeyHere": 55 } };
console.log(checkStepConditional(form2, card2));
// 55 < 75 && 55 > 60 ? show: false
Related
I want the difference in such a way that the I don't return the entire nested object if any of the values is different.
I have seen solutions online and they all return the entire nested objects and it doesn't work if only 1 key-value pair is changed. i don't want to show the difference as a complete nested object. it should be easier for any user to read.
for eg:
const A = {
position: 2,
attributes: [{
code: 123,
name: "xyz",
params: {
label: "hehe",
units: "currency"
}
}],
code: 1
}
const B = {
position: 3,
attributes: [{
code: 123,
name: "xyzr",
params: {
label: "heh",
units: "currency"
}
}],
code: 1
}
I want the output to be like this:
difference: {
position: {
current: 2,
previous: 3
},
attributes: {
current : [{ name: "xyz", params: { label: "hehe" } }],
previous: [{ name: "xyzr", params: {label: "heh"}}]
}
}
The code that I tried:
const compareEditedChanges = (A: any, B: any) => {
const allKeys = _.union(_.keys(A), _.keys(B));
try {
setDifference(
_.reduce(
allKeys,
(result: any, key) => {
if (!_.isEqual(A?.[key], B?.[key])) {
result[key] = {
current: A[key],
previous: B[key]
};
}
return result;
},
{}
)
);
} catch (err) {
console.error(err);
}
return difference;
};
After giving it a lot of thought to the code, I came with my own solution for a deeply nested objects comparison and listing out the differences in an object with keys as current and previous.
I didn't use any inbuilt libraries and wrote the code with simple for loop, recursion and map
const compareEditedChanges = (
previousState,
currentState
) => {
const result = [];
for (const key in currentState) {
// if value is string or number or boolean
if (
typeof currentState[key] === 'string' ||
typeof currentState[key] === 'number' ||
typeof currentState[key] === 'boolean'
) {
if (String(currentState[key]) !== String(previousState[key])) {
result.push({
[key]: {
current: currentState[key],
previous: previousState[key]
}
});
}
}
// if an array
if (
Array.isArray(currentState[key]) ||
Array.isArray(previousState[key])
) {
console.log(currentState[key])
if (currentState[key].length > 0 || previousState[key].length > 0) {
currentState[key].map((value, index) => {
// check for array of string or number or boolean
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean'
) {
if (
JSON.stringify(currentState[key]) !==
JSON.stringify(previousState[key])
) {
result.push({
[key]: {
current: currentState[key],
previous: previousState[key]
}
});
}
}
// check for array of objects
if (typeof value === 'object') {
const ans = compare(
value,
previousState[key][index]
);
result.push(ans);
}
});
}
}
}
return result;
};
You first need a object:
const [object, setObject] = useState({
number: 0,
text: "foo"
});
You need to check when the object changed with useEffect, but you also need to see the previos object, for that we will be using a helper function.
const prevObject = usePrevious(object);
const [result, setResult] = useState("");
useEffect(() => {
if (prevObject) {
if (object.number != prevObject.number) {
setResult("number changed");
}
if (object.text != prevObject.text) {
setResult("text changed");
}
}
}, [object]);
//Helper function to get previos
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Here is the Codesandbox
I am trying to create a class to find out the branch made the most in revenue.I believe, I need to sum all branch across multiple days and identify which branch had the highest sales overall. But I am kinda lost. Would anyone be able to help me? Thanks for the help
class SalesItem {
constructor(branch, totalSales, date) {
this.branch = branch;
this.totalSales = totalSales;
this.date = date
}
}
function CalculateBestBranch(sales) {
var branchSales = { key: "", value: 0 };
// TODO: order branchSales by value, highest first
// TODO: return the key of the highest value
const highestValue = 0;
return highestValue;
}
class SalesItem {
constructor(branch, totalSales, date) {
this.branch = branch;
this.totalSales = totalSales;
this.date = date;
}
}
const sales = [
new SalesItem('branch-1', 60, '2022-01-26'),
new SalesItem('branch-2', 90, '2022-01-26'),
new SalesItem('branch-1', 20, '2022-01-27'),
new SalesItem('branch-2', 30, '2022-01-27'),
];
function calculateBestBranch(sales) {
const branchSales = [];
sales.forEach((sale) => {
if (!branchSales.find((e) => e.key === sale.branch)) {
branchSales.push({ key: sale.branch, value: sale.totalSales });
} else {
let i = branchSales.findIndex((e) => e.key == sale.branch);
branchSales[i].value += sale.totalSales;
}
});
const sortedBranchSales = branchSales.sort((a, b) => b.value - a.value);
return sortedBranchSales[0];
}
calculateBestBranch(sales); // { key: 'branch-2', value: 120 }
I can get to limitfrom in the second case if I rename limitfrom to limitfrom2, but I don't understand how to get to the value without renaming the key. Maybe I'm doing the wrong thing about the destructuring itself.
create an object
const vedPlus = {
transferLegal: {
stageOne: {
limitfrom: 0,
limitUpTo: 60,
commission: 0
},
stageTwo: {
limitfrom2: 60,
limitUpTo2: 1000,
commission2: 29
},
}
then I do the destructuring in 4 steps.
const {transferLegal} = vedPlus
const {stageOne, stageTwo} = transferLegal
const {limitfrom, limitUpTo, commission} = stageOne
const {limitfrom2, limitUpTo2, commission2} = stageTwo
Create a function
function calc (i) {
if (i >= limitfrom && i <= limitUpTo) {
return i * commission
} else if (i >= limitfrom2 && i <= limitUpTo2) {
return i * commission2
} else {
console.log('error')
}
result
const price = 71
console.log(calc(price))
Is there a way to get to the value without renaming the key?
You can use the same property names for both inner objects, and then assign new unique names when destructuring to prevent conflicts, using const { prop: newName } = source syntax.
It may not produce the most readable code, but at least it can be done.
Demo:
const vedPlus = {
transferLegal: {
stageOne: { limitfrom: 0, limitUpTo: 60, commission: 0 },
stageTwo: { limitfrom: 60, limitUpTo: 1000, commission: 29 },
}
}
const { transferLegal } = vedPlus;
const { stageOne, stageTwo } = transferLegal;
const { limitfrom, limitUpTo, commission } = stageOne;
const { limitfrom: limitfrom2, limitUpTo: limitUpTo2, commission: commission2 } = stageTwo;
console.log(limitfrom, limitUpTo, commission);
console.log(limitfrom2, limitUpTo2, commission2);
Yes. But certainly not like this:
const {limitfrom, limitUpTo, commission} = stageOne;
const {limitfrom, limitUpTo, commission} = stageTwo;
Because you'd be re-declaring the same variables in the same scope. After these two lines, if you were to refer to the variable limitfrom, which one would you expect it to be and why?
If the keys have the same name, that's fine as long as they are unique within their context. In this case within their individual objects. You can use those objects to reference them:
const {stageOne, stageTwo} = transferLegal;
//...
if (i >= stageOne.limitfrom && i <= stageOne.limitUpTo) {
return i * stageOne.commission
} else if (i >= stageTwo.limitfrom && i <= stageTwo.limitUpTo) {
return i * stageTwo.commission
}
Basically, absolutely everything doesn't have to be destructured into its own variable with a single value. Objects exist for a reason, use them.
If you want to keep the data (vedPlus) as the same structure you can do the renaming while you are destructuring
const vedPlus = {
transferLegal: {
stageOne: {
limitfrom: 0,
limitUpTo: 60,
commission: 0
},
stageTwo: {
limitfrom: 60,
limitUpTo: 1000,
commission: 29
}
}
}
const {
transferLegal: {
stageOne: {
limitfrom,
limitUpTo,
commission
},
stageTwo: {
limitfrom: limitfrom2,
limitUpTo: limitUpTo2,
commission: commission2
}
}
} = vedPlus
function calc(i) {
if (i >= limitfrom && i <= limitUpTo) {
return i * commission
} else if (i >= limitfrom2 && i <= limitUpTo2) {
return i * commission2
} else {
console.log('error')
}
}
const price = 71
console.log(calc(price))
if the final goal is a simple calculation, then the use of the destructuring mechanism does not bring anything useful
const vedPlus =
{ transferLegal:
{ stageOne: { limitfrom: 0, limitUpTo: 60, commission: 0 }
, stageTwo: { limitfrom: 60, limitUpTo: 1000, commission: 29 }
} }
function calc(i)
{
let obj = vedPlus.transferLegal
for (let stage in obj)
{
if (obj[stage].limitfrom <= i && i <= obj[stage].limitUpTo)
return obj[stage].commission *i
}
throw `error: ${i} is out of range!`
}
console.log( '50 =>', calc(50) )
console.log( '500 =>', calc(500) )
console.log( '5000 =>', calc(5000) )
If this is important, I am working in a react mob-x store. I have an object that I am converting to an array and looping through and doing various things. I need to find the last instance of the object where the value === true to then have the key to use in a comparison. (ex. if(panelName === panel (of the last instance where the value is true).
I am having trouble finding the last item where value === true. I tried using arr.length -1 but that of course just finds the last one regardless of what the value is. The object key and length are both variable, the value is either 'true or false'. Thank you.
panelsSaved = {EE: true, SS: false, RR: false, FF: true, WW: false}
#action expandNextPanel(panelName){
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
for (const [panel, value] of panels){
if(value === true && panel !== panelName){
//do stuff
break;
}
}
I am upvoting most of the answers because it sort of took a combination of a few of them to get this working.
let panelsUsed = {EE: true, SS: false, RR: false, FF: true, WW: false};
if(panelsUsed.length !== 0) {
for (let i = 0; i <= panelsUsed.length; i++) {
if(panelsUsed[i] !== panelName){
if(panelsUsed[i] !== undefined) {
'do stuff'
break;
} else {
'do other stuff'
}
}
};
}
let panelsFiltered = panelsUsed.filter((panel) => {return panel !== panelName});
this.filterValuesData.panelsUsed = panelsFiltered;
} ```
Thank you everyone for your input!
simply :
const panelsSaved = { EE: true, SS: false, RR: false, FF: true, WW: false }
const LastTrue = obj => Object.entries(obj)
.filter(([k,v])=>v)
.reverse()[0][0]
console.log( LastTrue(panelsSaved) )
OR, with a Loop
const panelsSaved = { EE: true, SS: false, RR: false, FF: true, WW: false }
function LastTrue(obj)
{
let rep;
for (let key in obj) if (obj[key]) rep=key
return rep
}
console.log( LastTrue(panelsSaved))
Just use classical for loop in reverse order (for...of loop cannot be used in such way).
panelsSaved = {EE: true, SS: false, RR: false, FF: true, WW: false}
#action expandNextPanel(panelName){
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
for (var i = panels.length - 1; i >= 0; i--) {
const [panel, value] = panels[i]
if(value === true && panel !== panelName){
// do stuff
break;
}
}
}
let panelsSaved = { EE: true, SS: false, RR: false, FF: true, WW: false };
function expandNextPanel(panelName) {
let lastTrue = Object.entries(panelsSaved).filter(
([panel, value]) => value === true && panel !== panelName
).pop();
console.log(lastTrue)
}
expandNextPanel("FF");
There's quite many ways to approach this, but here's a couple of ways.
You could iterate the array in reverse order and return the first one that matches true:
function lastWhere(arr, fn) {
if (arr.length === 0) return undefined;
for (let i = arr.length - 1; i >= 0; i--) {
const candidate = arr[i];
if (fn(candidate)) {
return candidate;
}
}
return undefined;
}
// ...
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
const [panel, value] = lastWhere(panels, ([panel, value]) => value === true)
Or perhaps you could use .map() to map the values to true or false and use .lastIndexOf() to find the last one that's true:
function lastWhereTrue(arr, fn) {
const mapped = arr.map((candidate) => fn(candidate));
const matchingIndex = arr.lastIndexOf(true);
return mapped[matchingIndex];
}
// ...
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
const [panel, value] = lastWhere(panels, ([panel, value]) => value === true)
Then, if you need to go through the entire list of panels and do something to all of them and specifically do something to the last panel, you could just compare either the panel or the value, depending on which one is unique.
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
const last = lastWhere(panels, ([panel, value]) => value === true)
for (const [panel, value] of panels) {
if (panel === last.panel) {
// ...
}
// or
if (value === last.value) {
// ...
}
}
Example of the solution which I suggested in the comments:
const panelsSaved = this.filterValuesData.panelsSaved;
const panels = Object.entries(panelsSaved);
let lastTruePanel = null;
for (const [panel, value] of panels.reverse()) {
if (value === true && !lastTrueValueProcessed) {
// This is the entry which is the last value===true
lastTrueValueProcessed = [panel, value];
}
// ...
}
genderPie()
let filter = {};
async function genderPie() {
const d = await getData();
const g = await d.reduce((a, o) => (o.GEN && a.push(o.GEN), a), []);
const gender = Object.keys(g).length;
const m = await d.reduce((a, o) => (o.GEN == 1 && a.push(o.GEN), a), []);
const male = Object.keys(m).length;
const f = await d.reduce((a, o) => (o.GEN == 2 && a.push(o.GEN), a), []);
const female = Object.keys(f).length;
var data = [{
name: 'male',
y: male,
id: 1
}, {
name: 'female',
y: female,
id: 2
}];
chart = new Highcharts.Chart({
plotOptions: {
pie: {
innerSize: '80%',
dataLabels: {
connectorWidth: 0
}
}
},
series: [{
"data": data,
type: 'pie',
animation: false,
point: {
events: {
click: function(event) {
filter.GEN = '' + this.id + '';
}
}
}
}],
"chart": {
"renderTo": "gender"
},
});
}
async function getData() {
buildFilter = (filter) => {
let query = {};
for (let keys in filter) {
if (filter[keys].constructor === Array && filter[keys].length > 0) {
query[keys] = filter[keys];
}
}
return query;
}
//FILTER DATA
//Returns the filtered data
filterData = (dataset, query) => {
const filteredData = dataset.filter((item) => {
for (let key in query) {
if (item[key] === undefined || !query[key].includes(item[key])) {
return false;
}
}
return true;
});
return filteredData;
};
//FETCH JSON
const dataset = [{
"GEN": "2"
}, {
"GEN": "1"
}, {
"GEN": "1"
}, {
"GEN": "2"
},
{
"GEN": "2"
}, {
"GEN": "2"
}, {
"GEN": "2"
}, {
"GEN": "1"
}
]
//BUILD THE FILTER
const query = buildFilter(filter);
const result = filterData(dataset, query);
console.log(result)
return result
}
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="gender"></div>
does anyone can explain me how to handle the following?
I have two functions that filter data and than I build a chart with Hichart
Each time a user click for example a slice of a pie chart an event is fired and an object is populated.
That object allows me to filter the dataset and redraw the chart
The last thing I'm missing is about to update the filtering functions based on the object to be populated
first I'll do this
async function getData() {
buildFilter = (filter) => {
let query = {};
for (let keys in filter) {
if (filter[keys].constructor === Array && filter[keys].length > 0) {
query[keys] = filter[keys];
}
}
return query;
}
then
filterData = (data, query) => {
const filteredData = data.filter( (item) => {
for (let key in query) {
if (item[key] === undefined || !query[key].includes(item[key])) {
return false;
}
}
return true;
});
return filteredData;
};
const query = buildFilter(filter);
const result = filterData(data, query);
my object is
let filter = {}
when a user click the slice myobject become for example
let filter = {
gen: "1"
}
Take a look at this StackBlitz project.
In getData(), I simplified your filter to this one:
return data.filter(item => {
for (const property of Object.keys(filter)) {
if (item[property] !== filter[property]) {
return false;
}
}
return true;
});
and when a slice is clicked, I call genderPie() again, after updating the filter.
You might want to separate the data request from the filtering, so that the data is downloaded only once, not every time a filter is changed.