React JS : How to highlight partial data changes? - javascript

I have a React JS dashboard page that poll's a json URL every second. Most of the time the data is the same but as soon as part of it changes I would like to highlight the change part.
Is there a way to determine what parts of the data changed allowing me to highlight them for a second or two?

In your child components, implement componentWillReceiveProps where you can compare newly passed props to the current ones. If you spot a difference between the two objects, set state to something like "justChanged: true" and display accordingly.

If I understood right, you're probably needing something like this one: http://codepen.io/zvona/pen/XdWoBW
let originalJSON = {foo:'bar', foz: 'baz'}
let alteredJSON = {foo: 'barbar', fam: 'bam'}
const getOriginal = function(key, original, altered) {
const obj = {}
if (altered[key] && altered[key] !== original[key]) {
// highlight here, if needed
obj[key] = altered[key]
} else {
obj[key] = original[key]
}
return obj
}
const getAltered = function(key, original, altered) {
const obj = {}
if (original[key] || altered[key] === original[key]) {
return false
} else {
// highlight here, if needed
obj[key] = altered[key]
}
return obj
}
const getFinal = function(original, altered) {
return Object.keys(original)
.map((key) => getOriginal(key, original, altered))
.concat(
Object.keys(altered)
.map((key) => getAltered(key, original, altered))
)
.filter((item) => { return item })
.reduce((prev, next) => { return Object.assign(prev, next) })
}
let final = getFinal(originalJSON, alteredJSON)
In this case, final outputs: {foo: "barbar", foz: "baz", fam: "bam"}. Now the highlighting should be done in getOriginal and getAltered functions.

Related

Insert element inside array

I have a function
checkName(output) {
output.filter((NewData) => {
return this.props.elements.filter((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name,
//there i need to add another element
// Need to add newData.number = OldData.number
}
return NewData
})
})
return output
}
and I call this function like:
const named = this.checkName(product.rows)
Now I need to add to my product's array that I passed to checkName the value "OldData.Number" to "newData.Number" that is not defined in product (so I need to create this field)
For example:
Product before the checkName function
product.rows = [NewData.name]
Product after the checkName function
product.rows = [NewData.name="value of OldData.name", NewData.number="value of OldData.number"]
How can I obtain this result?
There are 2 confusing things in your code:
You are using filter to execute an action in each member of the output array. However, filter should be used to... well, filter that array, meaning that is should not modify it, just return a sub-set of it. Instead, you might want to use forEach. However, taking into accound the next bullet, probably you want to use map.
You are modifying the array passed to the checkName function. This is confusing and can lead to hard-to-find bugs. Instead, make your function "pure", meaning that it should not mutate its inputs, instead just return the data you need from it.
I would suggest some implementation like this one:
checkName(output){
return output.map((NewData) => {
// find the old data item corresponding to the current NewData
const OldData = this.props.elements.find(x => x.key === NewData.key);
if (OldData) {
// If found, return a clone of the new data with the old data name
// This uses the spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
return {
...NewData, // Clone the NewData object
name: OldData.name, // set the value found in OldData.name in the "name" field of the cloned object
number: OldData.number, // You can do the same for each field for which you want to replace the value cloned from NewValue
};
} else {
// Otherwise, just return a clone of the NewData
return { ...NewData };
}
}
}
The usage would be like this:
const named = this.checkName(product.rows)
Be aware that the product.rows array won't be modified!
You can get keys and values of the old object.
const keys = Object.keys(oldObject);
const values = Object.values(oldObject);
// or
const [keys, values] = Object.entries(oldObject);
After, you will create a loop with all keys of oldObject, and insert in newObject like a array.
keys.forEach( (key, index) => newObject[key] = values[index]);
// or
for (const [key, value] of Object.entries(object1)) {
newObject[key] = value
}
Use map like this.
checkName(output){
return output.map(( NewData) =>{
this.props.elements.forEach((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name;
NewData.number = OldData.number;
}
})
return NewData;
})
// return output;
}

Multiple If Else Statements, only first is run

I have code that requires multiple If Else statements but I'm not sure how to format it so that each runs:
let example = first;
let example2 = second;
let example3 = third;
if (example === something) {
return null;
} else {
return something;
}
if (example2 === somethingElse) {
return null;
} else {
return somethingElse;
}
if (example3 === somethingMore) {
return null;
} else {
return somethingMore;
}
But this doesn't work because of the multiple else statements, I was wondering if there was a way to do this? I also tried to put the data into an array or objects to iterate through but that won't work either.
Please help! :)
return will immediate return from first if, so store all result in object or array and return it as below
let example = 'first';
let example2 = 'second';
let example3 = 'third';
var return_data = {};
if (example === 'something') {
return_data.example = null;
} else {
return_data.example = something;
}
if (example2 === 'somethingElse') {
return_data.example2 = null;
} else {
return_data.example2 = 'somethingElse';
}
if (example3 === 'somethingMore') {
return_data.example3 = null;
} else {
return_data.example3 = 'somethingMore';
}
return return_data;
You have to remove the return in the if / else blocks - using return will immediately exit the function wherever it's encountered. The way your code is now, you are basically short-circuiting the function (which is not what you're trying to do):
It would probably make more sense to restructure your code to use a variable like this:
//Add a variable to keep store your desired output if you want to flow thru all if/else blocks
function getVal(example) {
let val;
if (example === 'something1') {
val = 'a'
} else {
val = 'b';
}
return val;
}
console.log(getVal('something1'));
console.log(getVal('lorem'));
I'm not completely clear on what you are asking, but I think you want to be using "else if" statements: https://ncoughlin.com/javascript-notes-conditional-statements-loops/#If_Else_If_Else
let example = first;
let example2 = second;
let example3 = third;
if (example === something) {
return a;
} else if (example2 === somethingElse){
return b;
} else if (example3 === anotherThing){
return c;
} else {
return null;
}
You can do something like this :
myArray = [];
let example = first;
let example2 = second;
let example3 = third;
if (example === something) {
myArray.push(null);
} else {
myArray.(something);
}
if (example2 === somethingElse) {
myArray.push(null);
} else {
myArray.(somethingElse);
}
if (example3 === somethingMore) {
myArray.push(null);
} else {
myArray.(somethingMore);
}
return myArray;
Like Tom O. said return will immediatly exit your function. You can use something other than an array but remember return is executed only once.
Regardless of your approach, it seems like you want to build a "collection" of some sort (array, object, set, map, etc) then return it at the end.
But, the way you code it depends on the reason your function exists. Let's look at an example...
if (first === undefined) {
return null
} else {
return first
}
...This logic exists solely to ensure a "default" value is used for first - something like the null object pattern. For this use case, I might propose nullish coalescing to keep it simple (or something that could be easily replaced with it in the future):
first ?? null
// or, if you don't use babel/some kind of transpiler, you could want:
first !== undefined && first !== null ? first : null
// and since our default is null anyway, we can shorten this to:
first !== undefined ? first : null
Looking solely at your example, it seems like you could simply want to get default values like this for multiple variables. For that use case, you (or someone else coming across this question) might want a function similar to one in the code snippets below. Using objects and/or arrays for this can be handy because they can also be easily broken back out into multiple variables, if you wanted.
First, example functions using arrays:
// If you want default values for items in an array (static, all same default value)
const buildArrayWithDefault = (vals, defaultVal = null) => vals.map(
v => v !== undefined ? v : defaultVal // could be v ?? defaultVal
)
// If you want default values for items in an array (static, but defaults could all be different)
const buildArrayWithDefaults = (vals, defaultVals) => vals.map(
(v, idx) => v !== undefined ? v : defaultVals[idx] // could be v ?? defaultVals[idx]
)
// If you want default values for items in an array (dynamic via callback)
const buildArrayWithDefaults2 = (vals, getDefaultValue) => vals.map(
(v, idx) => v !== undefined ? v : getDefaultValue(v, idx)
)
// All of these return [ 1, 5, 3 ]
console.log(
buildArrayWithDefault([1, undefined, 3], 5),
buildArrayWithDefaults([1, undefined, 3], [ 4, 5, 6 ]),
buildArrayWithDefaults2([1, undefined, 3], (v, idx) => idx + 4)
)
Next, examples using objects:
// Hard-coded default values for an object (ternary)
const buildObject = (first, second, third) => ({
first: first !== undefined ? first : null, // or first ?? null
second: second !== undefined ? second : null,
third: third !== undefined ? third : null,
})
// Hard-coded default values for an object (default parameters)
const buildObject2 = (
first = null,
second = null,
third = null
) => (
{ first, second, third }
)
// ...or you can just use Object.assign()
const assignDefaults = (obj) => Object.assign(
{ first: null, second: null, third: null }, // defaults
obj
)
// Finally, allowing the function user to define their own defaults
// (At this point, you may just want to use Object.assign() directly)
const assignDefaults2 = (...args) => Object.assign({}, ...args.reverse())
// All of these should return { first: 1, second: null, third: null }
console.log(
buildObject(1),
buildObject2(1),
assignDefaults({ first: 1 }),
assignDefaults2({ first: 1 }, { first: null, second: null, third: null })
)

Remove an object's key and value using a variable from function

Hey I'm trying to remove a key:value pair from state inside a Javascript Object.
It works when I hardcode the key name in the code, but when I try to use a variable from a function call, it does nothing.
Can somebody help me out?
Here's an object example:
toppingsSelected: {
"Onion":"true",
"Mushrooms":"true",
}
This works, hardcoded:
deleteTopping = toppingName => {
const { Onion, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns object without onion
};
This doesn't work:
deleteTopping = toppingName => {
const toppingName = "Onion"; // Variable gets passed in
const { toppingName, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns original object, no change made
};
So I'm basically trying to remove a key from React state but I'm pretty new to Javascript.
How can I make Javascript aware that toppingName is a key?
Another option is to add square brackets arround toppingName, and assign it to a variable. As #Bergi pointed out in the comments, this option does not mutate toppingsSelected
const toppingsSelected = {
"Onion":"true",
"Mushrooms":"true",
};
const toppingName = "Onion";
const {
[toppingName]: topping,
...withoutOnion
} = toppingsSelected;
console.log(JSON.stringify(withoutOnion));
To set the React state, you'd then do this
this.setState({ toppingsSelected: withoutOnion })
You can use delete e.g.
delete toppingsSelected[toppingName];
One way of doing this is using Array.prototype.filter()
const _obj = {
'Onion': true,
'notOnion': false
};
const newObj = Object.keys(_obj)
.filter(key => key !== 'Onion')
.reduce((acc, cur) => ({ ...acc, cur }), {})
console.log(newObj); // { notOnion: false }
This will return a new object without the 'Onion' property

multiple items local storage javascript

I have two different lists on my page, which data I want to store in Local Storage. With this code below I store my data from one list.
How can I use this code to store this data from two lists, but to be in separate objects?
maybe this question is silly, but i'm a beginner. Thanks for your help.
class Storage {
saveStorage(task) {
const tasks= this.getFromStorage();
tasks.push(task);
localStorage.setItem('tasks', JSON.stringify(tasks));
}
removeStorage(task){
const tasks= this.getFromStorage();
recipes.forEach((task, index) => {
if(task === task.id) {
tasks.splice(index,1);
}
});
localStorage.setItem('tasks', JSON.stringify(tasks));
}
getStorage() {
let tasks;
if(localStorage.getItem('tasks') === null) {
tasks= [];
} else {
tasks= JSON.parse(localStorage.getItem('tasks'));
}
return tasks;
}
}
Pass a name into the constructor, e.g.:
constructor(name) { this.name = name; }
Then replace tasks with this.name everywhere, e.g.:
localStorage.getItem(this.name)
Then you can just do:
const tasks = new Storage("tasks");
const users = new Storage("users");
If I understand your question correct, you need to adjust your structure of your functions to take in a parameter that defines the list you want to edit.
You storage should then contain an Array of Objects, and each object needs to have an identifier, such as a name or ID that can then be used to identify the different lists.
You would then need to pass them to your functions to modify and retrieve. Pass in that exact ID or name and your functions code should then be made to look for the right object in the array. Can easily be done with array.find, such as someArray.find(x = >x.name === "List1").
Hope it helps.
Try something like this that is a little more generic and reusable.
class Storage {
static saveStorage(obj, key) {
const store = Storage.getFromStorage(key);
store.push(obj);
localStorage.setItem(key, JSON.stringify(store));
}
static removeStorage(obj, key, fnCompare) {
const store = Storage.getFromStorage(key);
store.forEach((item, index) => {
if (fnCompare && fnCompare(obj, item)) {
store.splice(index, 1);
} else if (item == obj) {
store.splice(index, 1);
}
});
Storage.saveStorate(store, key);
}
static getFromStorage(key) {
if (localStorage.getItem(key) === null) {
return [];
} else {
return JSON.parse(localStorage.getItem(key));
}
}
}
Then you can call it like this:
Storage.saveStorage({ test: 'true'}, 'test');
Storage.removeStorage({ test: 'true'}, 'test', function(curr, prev) { return curr.test == prev.test});
Storage.getFromStorage('test')
I would use something more generic. With "tasks" hard-coded in there, it makes things difficult to reuse. Also, this is built so all stored data MUST be an array or items in an array - I would change it to handle all types. If you want to inject something into an array, I would think you can and should do that outside of the Storage class and just save again. Also, complex objects will need to be "revived" when pulled back out.
One other thing to note is that you may run into the "JSON Graph Problem" because JSON doesn't store object reference data. Use this with care. Here's a little more information on that: http://netflix.github.io/falcor/documentation/jsongraph.html.
See the example code below and here's a working jsfiddle to demonstrate: https://jsfiddle.net/wd4acLfv/45/
class Storage {
static get(key, reviveFuncOrDefault = null) {
const itemJSON = localStorage.getItem(key)
if (reviveFuncOrDefault !== null) {
if (itemJSON === null)
return reviveFuncOrDefault
else if (typeof reviveFuncOrDefault === 'function')
return reviveFuncOrDefault(JSON.parse(itemJSON))
}
return JSON.parse(itemJSON)
}
static set(key, item) {
localStorage.setItem(key, JSON.stringify(item))
}
static unset(key) {
localStorage.removeItem(key)
}
}
// Plain Object
Storage.set('obj', { foo: 'bar '}) // Set object
console.log(Storage.get('obj', {})) // Object exists
console.log(Storage.get('obj2', {})) // Object fallback value
// Primitive Type
Storage.set('bool', true) // Set array
console.log(Storage.get('bool', false)) // Array exists
console.log(Storage.get('bool2', false)) // Array fallback value
// Array
Storage.set('arr', [ 1, 2, 3 ]) // Set array
console.log(Storage.get('arr', [])) // Array exists
console.log(Storage.get('arr2', [])) // Array fallback value
// Mutate array
const arr = Storage.get('arr', [])
arr.push(4)
Storage.set('arr', arr)
console.log(Storage.get('arr', []))
// Non-plain JS Object
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
getFullName() {
return this.firstName + ' ' + this.lastName
}
}
Storage.set('person', new Person('Bob', 'Ross')) // Set object
const person = Storage.get('person',
({ firstName, lastName }) => new Person(firstName, lastName)
)
console.log(person.getFullName())

Shortest way to set value to variable in Knockout

In Knockout I have observable variable location. It is of type LocationEdit. This viewModel has observable and not fields.
I have collection of field names : fields. For each field I want to reset values for location
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
if (ko.isObservable(this.location()[field])) {
this.location()[field](locs[0][field]);
} else {
this.location()[field] = locs[0][field];
}
}
});
To make this code more simpler (remove if-clauses), Can I somehow set value to this.location()[field] in one line?
You could use the conditional operator (... ? ... : ... ;) although it doesn't change much:
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
ko.isObservable(this.location()[field]) ? this.location()[field](locs[0][field]) : this.location()[field] = locs[0][field];
}
});
Or you could write a function:
function upd(arr, index, val) {
ko.isObservable(arr[index]) ? arr[index](val) : arr[index] = val;
}
Usage:
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
upd(this.location(), field, locs[0][field]);
}
});
See demo.
You could even add this function to ko:
if(typeof ko.updatePotentialObservable == 'undefined')
ko.updatePotentialObservable = function (arr[index], val) {
ko.isObservable(obj) ? arr[index](val) : arr[index]= val;
}
Usage:
fields.forEach(field => {
if (this.uniqueField(locs, field)) {
ko.updatePotentialObservable(this.location(), field, locs[0][field]);
}
});
See other demo
To be honest, I think Gôtô's answers are definitely your best options. Basically, you'd want to create a utility function similar to ko.unwrap but setting a value.
But since you said "also want to find another solution", here's a different utility function. I think the most confusing part of your code is the returning calls to locs[0][field] and this.location()[field]. I'd want something with this signature:
reset(source, target, keys);
So, in your code, you could do:
reset(
this.location(),
locs[0],
fields.filter(f => this.uniqueField(locs, f))
);
Now, writing this method, I ended up with this:
const mergePropsObs = (function() {
// Return a method for setting a specific property in object
const getSetter = obj => prop => ko.isObservable(obj[prop])
? obj[prop]
: val => obj[prop] = val;
// Return unique keys for two objects
// (I went with a quick oneliner; there are many ways to do this)
const allKeys = (obj1, obj2) =>
Object.keys(Object.assign({}, obj1, obj2));
return (base, ext, onlyProps) => {
const props = onlyProps || allKeys(base, ext);
const values = props.map(p => ko.unwrap(ext[p]));
props
.map(getSetter(base))
.forEach((setter, i) => setter(values[i]));
};
}());
var base = { a: 1, b: ko.observable(2), c: 5 };
mergePropsObs(
base,
{ a: 2, b: 3 },
["a", "b"]);
console.log(base.a);
console.log(base.b());
console.log(base.c);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
This utility method can be used with the signature mentioned above. It also has a fallback for when you don't provide an array of field names.

Categories

Resources