Is this the correct way to store multiple observable maps within a store.
I'm relatively new to working with observables and MobX. I have a store for any maintenance domain object. For example "Institution Types". Instead of using a map for each I initiate a new map based off of an enum created:
maintenanceRegistry = new Map<string, any>();
selectedMaintenanceList = new Map<string, any>();
constructor() {
makeAutoObservable(this);
Object.values(MaintenanceTypeEnum).forEach((value) => {
this.maintenanceRegistry.set(value, new Map<string, any>());
});
}
And then on my onClick I set the selectedMaintenance map using the enum as the key:
if (maintenanceRegistry.get(MaintenanceTypeEnum.InstitutionTypes).size <= 1) {
loadMaintenance<InstitutionType>({apiPath: '/institutionTypes'});
} else {
setSelectedMaintenanceList(MaintenanceTypeEnum.StructureCategories);
}
When I load the data it updates my table and values however when the data has already been fetched the table data is not being updated.
To fetch the data I am using a computed function:
get maintenanceValues() {
var mv: any[] = [];
[...this.selectedMaintenanceList.values()].map((value: {value: any}, i) => {
mv.push(value);
})
return mv;
}
and then I map the data without the id property as its a guid and unnecessary:
const data = (maintenanceValues).map(value => {
const { id, ...noId } = value;
return noId
})
Related
I want to add some data on the bookChapters object, like a random id and inside of it the name of the chapters, I tried this but it doesn't work, after I add the previous data I also want to add a new object "takeAways", like the previous one, inside the random id object.
export const createNewChapter = (bookId, inputText) => {
return async dispatch => {
dispatch(createNewChapterStart());
try {
firebase
.firestore()
.doc(`Users/${bookId}/bookChapters/${inputText}`)
.onSnapshot(querySnapshot => {
//There I want to add the chapters to the firestore database
});
dispatch(createNewChapterSuccess(inputText));
} catch (error) {
dispatch(createNewChapterFail(error));
console.log(error);
}
};
};
I wanna know how to do add from scratch the bookChapters object
The database screenshot shows that the bookChapters object is a map. So to add (populate) this object you need to generate a simple JavaScript object with some properties as “key: value” pairs.
Something along these lines, making the assumption the chapter titles are in an Array:
function arrayToObject(arr) {
var obj = {};
for (var i = 0; i < arr.length; ++i) {
obj[i] = arr[i];
}
return obj;
}
const chapterList = ['Intro', 'Chapter 1', 'Chapter2', 'Conclusion'];
const bookChaptersObj = arrayToObject(chapterList);
firebase.firestore().doc(`Users/${bookId}`).update(bookChaptersObj);
Or, if the document does not already exist:
firebase.firestore().doc(`Users/${bookId}`).set(bookChaptersObj, {merge: true});
I am uploading the data from excel file using react-excel-renderer, storing the excel render response containing column & rows response in state and passing to other component.
Expected Use-case result:- I am fetching the data from excel using render storing the values in states(rows). I am passing the state to other component where i need these values to pass in API .
The data stored is in nested form. Can you please let me know how to get data separately stored under array in props. Attached is the screenshot.
Excel Render code:-
changeHandler(event) {
let fileObj = event.target.files[0];
//just pass the fileObj as parameter
ExcelRenderer(fileObj, (err, resp) => {
if (err) {
console.log(err);
} else {
this.setState({
cols: resp.cols,
rows: resp.rows,
});
}
});
}
Code to fetch the prop data:-
for (let i = 0; i < this.props.data.length; i++) {
let stDate = this.props.data[i].startDate;let TripName = this.props.data[i].TripName;
let totalFare = this.props.data[i].totalFare;
let FirstName = this.props.data[i].FirstName;
let LastName = this.props.data[i].LastName;
let Currency = this.props.data[i].Currency;
}
You can use an Array method
Still not 100% sure what your final data should look like, but it feels like you're using 2 arrays. 1 as the key and 1 as the value.
So to combine these 2 we can use Reduce
const keys = data[0];
const values = data[1];
keys.reduce((acc, keys, index) => {
return {...acc, [key]: values[index]}
}, {})
That will return an object of key values.
I am working with Flutter and am currently trying to create a graph. I am looking to parse this JSON Array from the link below. My issue is that the information provided in the "prices" object, the values are all inside arrays themselves. I want to get those values and split them into an X and Y list but I have no idea how to accomplish this. I posted a snippet of the JSON data below.
https://api.coingecko.com/api/v3/coins/bitcoin/market_chartvs_currency=usd&days=1
I am only familiar with parsing data by creating a class and constructor. Then create a fromJSON(Map<String, dynamic> json) class and putting the data into a list, as shown in the code snippet below that I created from another URL with object values. How could I go about parsing this array JSON data into two list data?
CODE TO PARSE JSON
List<Coins> _coins = List<Coins>();
Future<List<Coins>> fetchCoins() async {
var url = 'URL';
var response = await http.get(url);
var coins = List<Coins>();
if (response.statusCode == 200) {
var coinsJSON = json.decode(response.body);
for (var coinJSON in coinsJSON) {
coins.add(Coins.fromJson(coinJSON));
}
}
return coins;
}
#override
void initState() {
fetchCoins().then((value) {
setState(() {
_coins.addAll(value);
});
});
super.initState();
}
class Coins{
String symbol;
String name;
Coins(this.symbol, this.name);
Coins.fromJson(Map<String, dynamic> json) {
symbol = json['symbol'];
name = json['name'];
JSON DATA SNIPPET
{
"prices":[
[
1566344769277,
10758.856131083012
],
[
1566345110646,
10747.91694691537
],
[
1566345345922,
10743.789313302059
],
]
}
EDIT: SOLVED WITH THE HELP OF #EJABU.
class HistoricalData {
List prices;
List<num> listX = [];
List<num> listY = [];
HistoricalData(this.prices,this.listX, this.listY);
HistoricalData.fromJson(Map<String, dynamic> json) {
prices = json['prices'];
for (var price in prices) {
listX.add(price[0]);
listY.add(price[1]);
}
}
You may try this...
New class Coins definition:
class Coins {
List<num> listX = [];
List<num> listY = [];
Coins(this.listX, this.listY);
Coins.fromJson(Map<String, dynamic> json) {
List<List<num>> prices = json['prices'];
for (var price in prices) {
listX.add(price[0]);
listY.add(price[1]);
}
}
}
Then later you can fetch it by these lines :
// Future<List<Coins>> fetchCoins() async { // Remove This
Future<Coins> fetchCoins() async {
var url = 'URL';
var response = await http.get(url);
// var coins = List<Coins>(); // Remove This
Coins coins;
if (response.statusCode == 200) {
var coinsJSON = json.decode(response.body);
// Remove This
// for (var coinJSON in coinsJSON) {
// coins.add(Coins.fromJson(coinJSON));
// }
//
coins = Coins.fromJSON(coinsJSON);
}
return coins;
}
Accessing Data in Widget
In Widgets , our expected variable resides as property inside Coins class.
For example, if you use FutureBuilder, you may use these lines:
child: FutureBuilder(
future: fetchCoins(),
builder: (_, __) {
return SomeChartWidget(
listX: coins.listX,
listY: coins.listY,
);
},
),
Generating Serializers automatically
I suggest you take a look at https://pub.dev/packages/json_serializable, which is a package that does the boilerplate code generation for you. Although it might me a bit overkill to add something like this to your code or your workflow, automatically generating serializers is very convenient.
Not that in order to have custom sub-classes, they need to provide serialization as well.
If you want to extend your knowledge even further, you can also have a look at https://pub.dev/packages/built_value and https://pub.dev/packages/built_value_generator
Adding a node to a list and yet a component is not re-rendering. Mobx Chrome Extension dev tools says it's a dependency but for some reason still no reaction!
A button renders 'Add' or 'Remove' based on whether a node is in a list. It doesn't re-render unless I move to another component and then open this component again.
Buttons:
#inject("appStore") #observer
class EntityTab extends Component {
...
render() {
return (
...
{/* BUTTONS */}
{ this.props.appStore.repo.canvas.graph.structure.dictionary[id] !== undefined ?
<div onClick={() => this.props.appStore.repo.canvas.graph.function(id)}>
<p>Remove</p>
</div>
:
<div onClick={() => this.props.appStore.currRepo.canvas.otherfunction(id)}>
<p>Add</p>
</div>
}
...
)
}
}
The Add button renders, I click on the button which triggers
this.props.appStore.currRepo.canvas.otherfunction(id)
and then we go to this function
#observable graph = new Graph();
...
#action
otherfunction = (idList) => {
// Do nothing if no ids are given
if (idList === null || (Array.isArray(idList) && idList.length === 0)) return;
// If idList is a single value, wrap it with a list
if (!Array.isArray(idList)) { idList = [idList] }
let nodesToAdd = [];
let linksToAdd = [];
// Add all new links/nodes to graph
Promise.all(idList.map((id) => { return this.getNode(id, 1) }))
.then((responses) => {
for (let i = 0; i < responses.length; i++) {
let res = responses[i];
if (res.success) {
nodesToAdd.push(...res.data.nodes);
linksToAdd.push(...res.data.links);
}
}
this.graph.addData(nodesToAdd, linksToAdd, idList, this.sidebarVisible);
});
};
The getNode function creates new Node objects from the data. For reference, those objects are instantiated as such
export default class Node {
id = '';
name = '';
type = '';
constructor(r) {
for (let property in r) {
// Set Attributes
this[property] = r[property];
}
}
}
anyway, the addToGraphFromIds triggers
this.graph.addData(nodesToAdd, linksToAdd);
and then we go to that function
#action
addData = (nodes, links) => {
this.structure.addNodes(nodes);
this.structure.addLinks(links);
};
which triggers
this.structure.addNodes(nodes);
which leads to this function
#observable map = new Map();
#observable map2 = new Map();
#observable dictionary = {};
#observable dictionary2 = {};
#observable allNodes = [];
#observable allLinks = [];
...
#action
addNodes = (nodes=[]) => {
if (!nodes || nodes.length === 0) return;
nodes = utils.toArray(nodes);
// Only consider each new node if it's not in the graph or a duplicate within the input list
nodes = _.uniqBy(nodes, (obj) => { return obj.id; });
const nodesToConsider = _.differenceBy(nodes, this.allNodes, (obj) => { return obj.id; });
// Add nodes to map
let currNode;
for (let i = 0; i < nodesToConsider.length; i++) {
currNode = nodesToConsider[i];
this.map.set(currNode.id, new Map());
this.map2.set(currNode.id, new Map());
this.dictionary[currNode.id] = currNode;
}
// Update internal list of nodes
this.allNodes = this.allNodes.concat(nodesToConsider);
};
As we can see in the first codebox,
this.props.appStore.repo.canvas.graph.structure.dictionary[id] !== undefined
Should cause the button to change values as we have added the current node. The nodes appear in the dictionary when I log or use mobx chrome extension dev tools, but I have to switch tabs and then the button will re-render. I've tried using other lines like
this.props.appStore.repo.canvas.graph.structure.allNodes.includes(node)
but that doesn't work either. Am absolutely stuck and need help. I have a feeling it has to do with nested observables, and maybe tagging #observable isn't good enough, but not quite sure. repo and canvas are marked as observables and instantiate a new Repo() object and new Canvas() object, much like new Node() is created in getNodes.
Mobx (v4) does not track addition or removal of entries in an observable object unless observable.map is used. Or upgrading to mobx v5 should solve the issue.
For your specific issue you can try:
#observable nodeIdToNodeData = {};
//...
this.nodeIdToNodeData = {...this.nodeIdToNodeData, [currNode.id]: currNode};
Or try to upgrade to mobx v5.
More info here
Looks like Edward solved it, similar to Redux it looks like you have to create a new object with the dictionary rather than modify it. I'm guessing it's because the key currNode.id is already defined (as the value undefined), and we're just modifying it to be currNode. That's my guess, regardless it works now.
I have an array of Threads Objects with ID, title and a isBookmarked:boolean.
I've created a Subject and I want to subscribe to it in order to get an array of Thread Objects with isBookmarked=true.
https://plnkr.co/edit/IFGiM8KoSYW6G0kjGDdY?p=preview
Inside the Service I have
export class Service {
threadlist:Thread[] = [
new Thread(1,'Thread 1',false),
new Thread(2,'Thread 2',true),
new Thread(3,'Thread 3',false),
new Thread(4,'Thread 4',true),
new Thread(5,'Thread 5',true),
new Thread(6,'Thread 6',false),
new Thread(7,'Thread 7',false),
]
threadlist$:Subject<Thread[]> = new Subject<Thread[]>()
update() {
this.threadlist$.next(this.threadlist)
}
}
in the component
export class AppComponent implements OnInit {
localThreadlist:Thread[];
localThreadlistFiltered:Thread[];
constructor(private _service:Service){}
ngOnInit():any{
//This updates the view with the full list
this._service.threadlist$.subscribe( threadlist => {
this.localThreadlist = threadlist;
})
//here only if isBookmarked = true
this._service.threadlist$
.from(threadlist)//????
.filter(thread => thread.isBookmarked == true)
.toArray()
.subscribe( threadlist => {
this.localThreadlistFiltered = threadlist;
})
}
update() {
this._service.update();
}
}
which Instance Method do I use in general to split an array?
Also is there a better way to do it?
Thanks
You would leverage the filter method of JavaScript array within the map operator of observables:
this._service.threadlist$
.map((threads) => {
return threads.filter((thead) => thread.isBookmarked);
})
.subscribe( threadlist => {
this.localThreadlistFiltered = threadlist;
});
See this plunkr: https://plnkr.co/edit/COaal3rLHnLJX4QmvkqC?p=preview.