I am receiving a json response from an API call. I need to store its keys, and create an array of an object. I am intending to this array of an object is created dynamically no matter the keys of the response.
I've already got the keys like this:
const json_getAllKeys = data => {
const keys = data.reduce((keys, obj) => (
keys.concat(Object.keys(obj).filter(key => (
keys.indexOf(key) === -1))
)
), [])
return keys
}
That returned an array (using a sample json):
['name','username', 'email']
But I am trying to use that array to create an array of object that looks like this one
[
{
name: "name",
username: "username",
email: "Email",
}
];
I've been trying mapping the array, but got multiple objects because of the loop, and I need a single one to make it work.
keys.map(i=>({i:i}))
[
{ i: 'id' },
{ i: 'name' },
{ i: 'username' },
{ i: 'email' }
]
Any hint would be useful!
Thanks in advance :D
What you're looking for is Object.fromEntries, which is ECMA2019, I believe, so available in Node >=14 and will be provided as a polyfill if you employ babel.
I can't quite discern what your reduce should produce, but given the sample input, I would write
const input = ['name','username', 'email'];
const result = Object.fromEntries(input.map(name => ([name, name])));
// result == { name: 'name', username: 'username', email: 'email' }
You're definitely on the right track. One thing to remember is the map function will return the SAME number of output as input. So in your example, an array of 3 returns an array of 3 items.
For this reason, map alone is not going to give you what you want. You may be able to map => reduce it. However, here is a way using forEach instead. This isn't a strictly functional programming style solution, but is pretty straight forward and depending on use case, probably good enough.
let keys = ['name','username', 'email'] //you have this array
const obj = {}; // empty object to hold result
keys.forEach(i => {
obj[i] = i; // set the object as you want
})
console.log(obj); // log out the mutated object
// { name: 'name', username: 'username', email: 'email' }
What I have: there is some json config (descriptive template), methods stored in diffrent order, its look like:
[
{
"name" : "methodA", //methodA output arguments are methodB input arguments
"inArgs" : "[arg1, arg2]",
"returnArgs" : "[arg3, arg4]"
},
{
"name" : "methodB", //methodB output arguments are methodZ input arguments
"inArgs" : "[arg3, arg5]",
"returnArgs" : "[arg6, arg7]"
},
{
"name" : "methodС",
"inArgs" : "[arg1]",
"returnArgs" : "[arg10]"
},
a bunch of other methods whose input arguments are not part of methodA or methodB
.....
{
"name" : "methodZ",
"inArgs" : "[arg6, arg11]",
"returnArgs" : "[arg20]"
}
]
I need to put these methods in the right order(chain) to run, like:
methodC //the output of this method is not used as an input argument to other methods
methodA //chain i need right order
methodB
methodZ
second case
[
.....
{
"name" : "methodX", //methodX output arguments are methodY input arguments
"inArgs" : «arg1, arg2, arg3]»,
"returnArgs" : «[arg4, arg5, arg6]»
},
{
"name" : "methodY", //methodY output arguments are methodX input arguments
"inArgs" : «[arg4, arg5, arg7]»,
"returnArgs" : «[arg8, arg9, arg10]»
},
....
{
"name" : "methodZ", //methodZ output arguments are methodX input arguments( collision or cycle, so throw error )
"inArgs" : «[arg8, arg11, arg12]»,
"returnArgs" : «[arg3, arg13, arg14]»
},
]
Because the output arguments of one method can be the input arguments of another method (also through a chain of methods of indefinite nesting), it is necessary to catch such collisions, preferably at the stage of parsing the config.
Can someone advise the optimal solution to such a problem, so far only graphs come to mind.
Sorry for my English.
(Sorry, this is a very long answer. I hope it's useful.)
An answer I like
I started trying to solve this using the API you were looking for. I did manage to get something reasonably close. But it wasn't something I would personally use. I rewrote the API and refactored the implementation several times until I came up with something I would like to use. Below I will discuss more of my early steps (which may be more relevant to you) but here is how I would use my version:
const def = {
url: (server, path, query, fragment) => `${server}/${path || ''}${query || ''}${fragment ? `#${fragment}` : ''}`,
query: (parameters) => parameters ? '?' + Object.entries(parameters).map(([k, v]) => `${k}=${v}`).join('&') : '',
server: (schema, port, host) => `${schema}:/\/${host}${port && (String(port) != '80') ? `:${port}` : ''}`,
host: (domain, subdomain) => `${subdomain ? `${subdomain}.` : ''}${domain}`,
}
const vals = {
schema: 'https',
port: '80',
domain: 'example.com',
subdomain: 'test',
path: 'path/to/resource',
parameters: {foo: 42, bar: 'abc'},
fragment: 'baz',
}
runFunctions (def) (vals)
This would generate an output like the following:
{
schema: "https",
port: "80",
domain: "example.com",
subdomain: "test",
path: "path/to/resource",
parameters: {foo: 42, bar: "abc"},
fragment: "baz",
query: "?foo=42&bar=abc",
host: "test.example.com",
server: "https://test.example.com",
url: "https://test.example.com/path/to/resource?foo=42&bar=abc#baz"
}
API Design
The main advantage I see in this version is that the API feels quite clean. The configuration object just maps names to functions and the data object supplied to the resulting function simply maps names to the initial parameters needed by those functions. The result is an enhanced version of that data object. The initial call returns a reusable function. It's all very simple.
Implementation
Some of the history of how I wrote this is embedded in the design. It probably could use a good refactoring; several of the helper functions are probably not necessary. But for now it consists of:
four trivial helper functions:
isEmpty reports whether an array is empty
removeIndex acts like an immutable splice, returning a copy of an array without its nth index
props maps an array of property names to their values in a given object
error simply wraps a string in an error and throws it
one less trivial helper function:
parseArgs retrieves the parameter names from a function. It is based on https://stackoverflow.com/a/9924463. (Oddly, the first one I tried, https://stackoverflow.com/a/31194949, which worked fine in my testing REPL, failed here in the StackOverflow snippets.)
four main functions:
preprocess converts our description object into a configuration object that looks something like the structure described in the question (with name and inArgs properties, although without returnArgs ones.)
makeGraph converts converts a configuration object into an adjacency graph (an array of objects with a name string and a predecessors array of strings.)
sortGraph performs a topological sort on an adjacency graph. It's borrowed from one I wrote at https://stackoverflow.com/a/54408852/, but enhanced with the ability to throw an error if the graph is cyclic.
process accepts the configuration object and the sorted graph and generates a unary function. That function takes a context object and applies the functions in order to the properties of that object, adding a new value to the object keyed to the function name. This calls makeGraph and then sortGraph on the result.
and, finally, a small wrapper function:
runFunctions accepts a description object, calls preprocess on it to create the configuration object, passing that to process and returns the resulting function.
I'm sure there's a reasonable refactoring that removes the need for the intermediate configuration object and/or one that combines the creation and sorting of the graph. That's left as an exercise for the reader!
Complete example
// helpers
const isEmpty = arr =>
arr .length == 0
const removeIndex = (n, arr) =>
arr .slice (0, n) .concat (arr .slice (n + 1) )
const props = (names) => (obj) =>
names .map (name => obj [name] )
const error = (msg) => {
throw new Error (msg)
}
// retrieves parameter named from a function (https://stackoverflow.com/a/9924463)
const parseArgs = (func) => {
var fnStr = func.toString().replace( /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '');
var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(/([^\s,]+)/g);
if(result === null)
result = [];
return result;
}
// chooses an appropriate order for our digraph, throwing error on circular
const sortGraph = (
graph,
sorted = [],
idx = graph .findIndex (node => isEmpty (node.predecessors) ),
nodeName = (graph [idx] || {}) .name
) => isEmpty (graph)
? sorted
: idx < 0
? error ('function definitions contains cycle')
: sortGraph (
removeIndex (idx, graph) .map (({name, predecessors}) => ({
name,
predecessors: predecessors .filter (n => n !== nodeName)
}), graph),
sorted .concat (nodeName)
)
// turns a config into an adjacensy graph
const makeGraph = config =>
Object .entries (config) .map (([name, {inArgs}]) => ({
name,
predecessors: inArgs .filter (name => name in config)
}) )
// turns a config object into a function that will run its
// functions in an appropriate order
const process = (config, order = sortGraph (makeGraph (config) )) =>
(vals) =>
order .reduce
( (obj, name) => ({
...obj,
[name]: config [name] .fn .apply (obj, props (config [name] .inArgs) (obj) )
})
, vals
)
// converts simpler configuration into complete version
const preprocess = (def) =>
Object .entries (def) .reduce
( (obj, [name, fn]) => ( { ...obj, [name]: {fn, inArgs: parseArgs(fn)} })
, {}
)
// main function
const runFunctions = (def) =>
process (preprocess (def) )
// input definition
const def = {
url: (server, path, query, fragment) => `${server}/${path || ''}${query || ''}${fragment ? `#${fragment}` : ''}`,
query: (parameters) => parameters ? '?' + Object.entries(parameters).map(([k, v]) => `${k}=${v}`).join('&') : '',
server: (schema, port, host) => `${schema}:/\/${host}${port && (String(port) != '80') ? `:${port}` : ''}`,
host: (domain, subdomain) => `${subdomain ? `${subdomain}.` : ''}${domain}`,
}
// initial input object
const vals = {
schema: 'https',
port: '80',
domain: 'example.com',
subdomain: 'test',
path: 'path/to/resource',
parameters: {foo: 42, bar: 'abc'},
fragment: 'baz',
}
console .log (
runFunctions (def) (vals)
)
Differences from requested design
The API in the question was different: the configuration object looked more like:
[{
name: 'makeUrl',
inArgs: '[domain, subdomain]',
returnArgs: '[host]',
}, /* ... */]
and even after some cleanup, would look like this:
[{
name: 'makeHost',
inArgs: ['domain', 'subdomain'],
returnArgs: ['host'],
}, /* ... */]
This is more flexible than my solution, as it allows multiple returns from a single function, wrapped in an array. But without some uncomfortable gymnastics in the implementation, it would also require multiple returns from each function. Moreover it would require that however you supplied your functions to this, you would have to match the function separately with the name, you would have to ensure that the argument names and order exactly matched the inArgs parameter, and you would have to wrap the more common scalar returns in an array. That might look something like this:
const fns = {
makeHost: (domain, subdomain) => [`${subdomain ? `${subdomain}.` : ''}${domain}`],
/* ... */
}
My Initial Approach
Adding a second configuration parameter and keeping them in sync makes for a much less ergonomic API in my opinion. But it can be done, and it was how I first approached the problem.
This version needed several fewer helper functions. There is no need for preprocess or parseArgs. props was only added to simplify the refactored version above. I haven't checked whether it would help with this one.
Note that process is substantially more complicated here and makeGraph is somewhat more complicated. That's because handling the multiple return arguments adds a fair bit of work. Overall, this version is a few lines shorter than the version above. That's often the trade-off when you create a more comfortable API. But the individual functions are less complex.
Implementation
You can expand this snippet to see a complete example:
// helpers
const isEmpty = arr =>
arr .length == 0
const removeIndex = (n, arr) =>
arr .slice (0, n) .concat (arr .slice (n + 1))
const error = (msg) => {
throw new Error (msg)
}
// chooses an appropriate order for our digraph, throwing error on circular
const sortGraph = (
graph,
sorted = [],
idx = graph .findIndex (node => isEmpty (node.predecessors) ),
nodeName = (graph [idx] || {}) .name
) => isEmpty (graph)
? sorted
: idx < 0
? error ('contains cycle')
: sortGraph (
removeIndex (idx, graph) .map (({name, predecessors}) => ({
name,
predecessors: predecessors .filter (n => n !== nodeName)
}), graph),
sorted .concat (nodeName)
)
// turns a config into an adjacensy graph
const makeGraph = config =>
config .map (({name, inArgs}) => ({
name,
predecessors: inArgs .flatMap (
input => config
.filter ( ({returnArgs}) => returnArgs .includes (input) )
.map ( ({name}) => name )
)
}) )
// main function
const process = (config) => (fns, order = sortGraph (makeGraph (config) )) =>
(vals) =>
order .reduce
( (obj, name) => {
const {inArgs, returnArgs} = config .find
( node => node .name == name
)
const args = inArgs .map (key => obj [key])
const res = fns [name] .apply (obj, args)
return returnArgs .reduce
( (o, k, i) => ({...o, [k]: res [i]})
, obj
)
}
, vals
)
const config = [
{name: 'host', inArgs: ['domain', 'subdomain'], returnArgs: ['host']},
{name: 'server', inArgs: ['schema', 'port', 'host'], returnArgs: ['server']},
{name: 'query', inArgs: ['parameters'], returnArgs: ['query']},
{name: 'url', inArgs: ['server', 'path', 'query', 'fragment'], returnArgs: ['url']}
]
const fns = {
host: (domain, subdomain) => [`${subdomain ? `${subdomain}.` : ''}${domain}`],
server: (schema, port, host) =>
[`${schema}:/\/${host}${port && (String(port) != '80') ? `:${port}` : ''}`],
query: (parameters) => [parameters ? '?' + Object.entries(parameters).map(([k, v]) => `${k}=${v}`).join('&') : ''],
url: (server, path, query, fragment) => [`${server}/${path || ''}${query || ''}${fragment ? `#${fragment}` : ''}`]
}
const vals = {
schema: 'https',
port: '80',
domain: 'example.com',
subdomain: 'test',
path: 'my/path',
parameters: {foo: 42, bar: 'abc'},
fragment: 'baz',
}
console .log (
process (config) (fns) (vals)
)
Intermediate Work
I wouldn't even attempt to show all the stages my code went through between the initial and final versions, but there was an interesting waypoint in the API, in which I used a configuration object like this:
const config = {
host: {
inArgs: ['domain', 'subdomain'],
fn: (domain, subdomain) => `${subdomain ? `${subdomain}.` : ''}${domain}`,
},
/* ... */
}
There is something to be said for that version: it avoids the need to parse the function in order to to get the parameters. The variety of fragile answers to How to get function parameter names/values dynamically? demonstrate that this is a non-trivial problem. And it should be quite familiar to users of Angular's dependency injection.
But in the end, this is just too much cleaner:
const config = {
host: fn: (domain, subdomain) => `${subdomain ? `${subdomain}.` : ''}${domain}`,
/* ... */
}
And hence I prefer my final version.
Conclusions
This is a non-trivial problem.
The implementation is not particularly difficult in any of these versions. But breaking it down into the useful pieces is challenging. And determining a useful API when we're afforded the flexibility to choose whatever seems right can take a lot of thought, a lot of discussion, and a lot of playing around.
Different developers will make different choices, often for important reasons, but to me sacrificing the likely rare facility to have multiple returns from a single function was entirely worth it to achieve a substantially simpler configuration object. In fact, it's difficult to imagine a simpler configuration.
An easier, but not-that-bulletproof (you cannot detect cycles) solution would be to wrap every value into a Promise: When a function generates certain outputs, resolve the Promise, then use Promise.all on the inputs. That way, the promises will automatically determine the right order:
const context = { /* [var: string]: { resolve(v: T), value: Promise<T> */ };
function getVar(name) {
if(context[name]) return context[name].value;
const cont = context[name] = { };
return cont.value = new Promise(res => cont.resolve = res);
}
function setVar(name, value) {
getVar(name); // Make sure prop is initialized, don't await!
context[name].resolve(value);
}
async function run(fn, argNames, resultNames) {
const args = await Promise.all(argNames.map(getVar));
const results = fn(...args);
for(let i = 0; i < results.length; i++)
setVar(resultNames[i], results[i]);
}
I want to return an Observable of one object, selected from an array of objects from my store. I would like to use the .single operator, because this would throw an exception if there are less or more than 1 objects present. I can't get it to work though, because my store is returning an array instead of an observable. I know I could use .filter on the array, but I would like to understand why I can't get it to work. I even tried using a selector.
My App reducer has:
export interface AppState {
batStores: fromBatStores.State;
}
export const reducers: ActionReducerMap<AppState> = {
batStores: fromBatStores.batStoresReducer,
};
The reducer of my module:
export interface State {
batStores: BatStore[];
}
const initialState = {
batStores: [
new BatStore(0, 0, true, 'Cell 11', 11, 11),
new BatStore(1, 0, false, 'Cel 12', 12, 11),
new BatStore(2, 0, true, 'Cel 13', 13, 11),
new BatStore(2, 0, true, 'Cel 14', 14, 11),
]
};
export const selectBatStores = (state: fromApp.AppState) => state.batStores;
export const selectBatStoresBatStores = createSelector(selectBatStores, (state: State) => state.batStores);
Now I try something like this in my ngOnInit:
ngOnInit() {
const editId = 11;
const batStore$: Observable<BatStore> = this.store.select(fromBatStores.selectBatStoresBatStores)
.single(bs => bs.number === editId);
this.batStore$ = batStore$;
}
PhpStorm says the number property doesn't exist on BatStore[]. Which I don't understand, because at the example documentation single is also used as an operator on observable array.
Update
After reading the answers, still very confused. To test, I wrote my function like this:
getBatStore(id: number) {
const versionA$ = this.store.select(fromBatStores.selectBatStoresBatStores)
.switchMap((batStores: BatStore[]) => Observable.from(batStores))
.filter((bs: BatStore) => {
console.log('Analyzing ', bs);
console.log('Result ', bs.number === id);
return bs.number === id;
});
const versionB$ = this.store.select(fromBatStores.selectBatStoresBatStores)
.switchMap((batStores: BatStore[]) => Observable.from(batStores))
.single((bs: BatStore) => {
console.log('Analyzing ', bs);
console.log('Result ', bs.number === id);
return bs.number === id;
});
console.log('VersionA: ', versionA$);
console.log('VersionB: ', versionB$);
console.log('Same? ', versionA$ === versionB$);
return versionB$;
}
Both versions output the same thing to the console. VersionA works when using with async pipe in my view, VersionB doesn't. The code compiles and PhpStorm doesn't complain.
The select is getting a reference to the whole array and so the number property does not exist on it. Try a switchMap to switch to a new observable that expands the list so they can be selected one at a time like so:
const batStore$: Observable<BatStore> = this.store.select(fromBatStores.selectBatStoresBatStores)
.switchMap((batStores : BatStore[]) => Observable.from(batStores))
.single((bs: BatStore) => bs.number === editId);
I understand your confusion, PhpStorm is right to say "the number property doesn't exist on BatStore[]". The example documentation is also correct. The key here, in this example from RxJs, is you need to know how the Observable.from([array]) works. An Observable.from([array]) creates a "cold" observable that does one emission for every item in the array. So, the resulting Observable is of type number, not of type array, as you can see here:
const source: Observable<number> = Rx.Observable.from([1,2,3,4,5]);
The Observable that are you facing in your store, is an Observable of type Array. For example if you use the operator Observable.of, this will emit the array, not every item of the array:
const source: Observable<number[]> = Rx.Observable.of([1,2,3,4,5]);
Said that, I would do an Array.filter, for me it's better than any other transformation on the Observable stream.
Let me know if you have any doubt,
Hope this helps.
The select method will retorn an array object, for that reason you cant access the number property. You could try the following:
ngOnInit() {
const editId = 11;
this.batStore$ = this.store.select(fromBatStores.selectBatStoresBatStores)
.flatMap(list => list)// flat list to single elements, emit 1 value per element
.single(bs => bs.number === editId);
}
Another approach would be to store the elements in your state as key,value pairs and then either create a selector that takes the Id argument, or store that Id in the state and then compose 2 selectors (1 for collection of elements, 1 for Id) into one.
I am wondering if there is a way I can construct mongo's queries to take advantage of es6 default parameters. I have the following method. I want to return all the data if make, model and year is not specified. I am trying to find an elegant solution but so far all I can think of is manual if else.
getStyles({ make = '', model = '', year = '-1' }) {
return this.db
.collection('styles')
.find({ 'make.niceName': make, 'model.niceName': model, 'year.year': parseInt(year) })
.toArray();
}
Note:
This is causing some confusion. I am using destructing on purpose. The problem is not how to write this function. The problem is how to construct a mongo query so it would ignore empty values.
Assuming getStyles is your own method, sure, you can give make, model, and year defaults. You can also give a default for the whole object you're destructuring so caller doesn't have to pass anything:
function getStyles({make = '', model = '', year = '-1'} = {}) {
// Overall default ------------------------------------^^^^^
return // ...
}
The question is not how to organize/write my function but how to use es6 features to write a cleaner code that would work with mongo. I.E if the user didn't pass anything I want to return all the styles but mongo actually looks for empty fields so it doesn't return anything.
It sounds to me like you don't want default parameters (except perhaps the overall default). Instead, you want to automate how you build the object you pass find.
Given your code example, you can readily do that with Object.keys on your object. So accept as an object, e.g.:
function getStyles(options = {}) {
...an then build your find options based on options:
const findParams = {};
Object.keys(options).forEach(key => {
findParams[key + ".niceName"] = options[key];
});
Live example:
function getStyles(options = {}) {
const findParams = {};
Object.keys(options).forEach(key => {
findParams[key + ".niceName"] = options[key];
});
console.log(`find options: ${JSON.stringify(findParams)}`);
}
let results = getStyles({make: "Ford", model: "Mustang"});
results = getStyles({make: "Ford", model: "Mustang", year: 2017});
If the mapping of the name you accept (make) to the name you need for find (make.niceName) isn't as easy as just appending .niceName, it's easy enough to have a Map (or just object) you build once:
const paramNames = new Map([
["make", "make.niceName"],
["model", "model.niceName"],
["year", "year.niceName"]
]);
...and then use:
const findParams = {};
Object.keys(options).forEach(key => {
const paramName = paramNames.get(key);
if (paramName) {
findParams[paramName] = options[key];
}
});
Live example:
const paramNames = new Map([
["make", "make.niceName"],
["model", "model.niceName"],
["year", "year.niceName"]
]);
function getStyles(options = {}) {
const findParams = {};
Object.keys(options).forEach(key => {
const paramName = paramNames.get(key);
if (paramName) {
findParams[paramName] = options[key];
}
});
console.log(`find options: ${JSON.stringify(findParams)}`);
}
let results = getStyles({make: "Ford", model: "Mustang"});
results = getStyles({make: "Ford", model: "Mustang", year: 2017});
Side note: Defaults don't have to be strings, so if you use numbers for year rather than strings, your default would just be -1, not '-1'.
Actually I need to handle mysite frontend fully with json objects(React and lodash).
I am getting the initial data via an ajax call we say,
starred[] //returns empty array from server
and am adding new json when user clicks on star buton it,
starred.push({'id':10,'starred':1});
if the user clicks again the starred should be 0
current_star=_findWhere(starred,{'id':10});
_.set(curren_star,'starred',0);
but when doing console.log
console.log(starred); //returns
[object{'id':10,'starred':0}]
but actually when it is repeated the global json is not updating,while am performing some other operations the json is like,
console.log(starred); //returns
[object{'id':10,'starred':1}]
How to update the global , i want once i changed the json, it should be changed ever.Should I get any idea of suggesting some better frameworks to handle json much easier.
Thanks before!
Working with arrays is complicated and usually messy. Creating an index with an object is usually much easier. You could try a basic state manager like the following:
// This is your "global" store. Could be in a file called store.js
// lodash/fp not necessary but it's what I always use.
// https://github.com/lodash/lodash/wiki/FP-Guide
import { flow, get, set } from 'lodash/fp'
// Most basic store creator.
function createStore() {
let state = {}
return {
get: path => get(path, state),
set: (path, value) => { state = set(path, value, state) },
}
}
// Create a new store instance. Only once per "app".
export const store = createStore()
// STARRED STATE HANDLERS
// Send it an id and get back the path where starred objects will be placed.
// Objects keyed with numbers can get confusing. Creating a string key.
const starPath = id => ['starred', `s_${id}`]
// Send it an id and fieldId and return back path where object will be placed.
const starField = (id, field) => starPath(id).concat(field)
// import to other files as needed
// Add or replace a star entry.
export const addStar = item => store.set(starPath(item.id), item)
// Get a star entry by id.
export const getStar = flow(starPath, store.get)
// Get all stars. Could wrap in _.values() if you want an array returned.
export const getStars = () => store.get('starred')
// Unstar by id. Sets 'starred' field to 0.
export const unStar = id => store.set(starField(id, 'starred'), 0)
// This could be in a different file.
// import { addStar, getStar, getStars } from './store'
console.log('all stars before any entries added:', getStars()) // => undefined
const newItem = { id: 10, starred: 1 }
addStar(newItem)
const star10a = getStar(10)
console.log('return newItem:', newItem === star10a) // => exact match true
console.log('star 10 after unstar:', star10a) // => { id: 10, starred: 1 }
console.log('all stars after new:', getStars())
// Each request of getStar(10) will return same object until it is edited.
const star10b = getStar(10)
console.log('return same object:', star10a === star10b) // => exact match true
console.log('return same object:', newItem === star10b) // => exact match true
unStar(10)
const star10c = getStar(10)
console.log('new object after mutate:', newItem !== star10c) // => no match true
console.log('star 10 after unstar:', getStar(10)) // => { id: 10, starred: 0 }
console.log('all stars after unstar:', getStars())
I think the problem is in mutating original state.
Instead of making push, you need to do the following f.e.:
var state = {
starred: []
};
//perform push
var newItem = {id:10, starred:1};
state.starred = state.starred.concat(newItem);
console.log(state.starred);
//{ id: 10, starred: 1 }]
var newStarred = _.extend({}, state.starred);
var curr = _.findWhere(newStarred, {id: 10});
curr.starred = 0;
state = _.extend({}, state, {starred: newStarred});
console.log(state.starred)
//{ id: 10, starred: 0 }]
To solve this in a more nice looking fashion, you need to use either React's immutability helper, or ES6 stuff, like: {...state, {starred: []}} instead of extending new object every time. Or just use react-redux =)