How to organize nested props in VueJS? - properties

I have this strange organization/architecture pickle I can't seem to figure out.
Say I have 3 components, A -> B -> C where A is a grandparent, B is a parent, and C is a child.
A passes an array of objects to B.
B loops through the array passing each object to instances of C.
Some of these objects are guaranteed not to change, while others may change.
C requires two fields in the object to be present, say object.name and object.icon. This would be specified in C's props.
My pickle is that a developer that is using component A has to look at C's props in order to know what properties to provide in the objects to B. And this problem only gets worse if I add more components between A and C.
How do I structure this?
I was thinking maybe this is where a store would come in, but I always thought stores were for maintaining state that can change. And some of the objects that A provides are guaranteed to never change.

I personally would use a shared state among components, or as you said, a store. Stores are specially useful for when you want to maintain some data between components, and when it changes in any of those, it will update in all the other components.
Simple example:
var sourceOfTruth = {
propThatChanges: {},
immutable: {}
}
var vmA = new Vue({
data: sourceOfTruth.propThatChanges
})
var vmB = new Vue({
data: sourceOfTruth.propThatChanges
})
Now whenever sourceOfTruth.propThatChanges is mutated, both vmA and vmB will update their views automatically. In the case of the objects that are guaranteed to not change, you could simply not pass it to the C components, just like sourceOfTruth.immutable. Or you could create an entire new store object available to A but not B or C
There is more info the the Vue docs, you should check it out:
http://vuejs.org/guide/application.html#State-Management

Related

What exactly does mobx-react-lite's "useLocalStore" hook do, and why is it (only sometimes) needed?

Within the mobx-react documentation there are variations in how stores are created. For example, on the React Context page:
In the first code sample, the store is instantiated with useLocalStore:
const store = useLocalStore(createStore)
In the second code sample, the stores are initiated by directly "newing" the stores":
counterStore: new CounterStore(),
themeStore: new ThemeStore(),
By inference, the first is a "local" store (and thus needs useLocalStore), and the second is a "global" store and thus doesn't. However, it is not clear why this is, and what the subsequent differnce in behaviour is.
Why is useLocalStore not needed in second example, and what difference does this make to the behavior of the stores and mobx within React?
Thanks for any input
OK, I found the answer. useLocalStore turns a javascript literal into a store with observable properties. This is not needed if a store is created from a class object with observable attributes.
Thanks to #freddyc for the answer
useLocalStore has been deprecated in favor of useLocalObservable, see here.
Links from the question are no longer valid, mobx-react docs are now here:
https://mobx.js.org/react-integration.html
useLocalObservable(initializer, annotations)
is just a shorthand for:
useState(() => observable(initializer(), annotations, {autoBind: true}))[0]
useLocalStore is almost the same, but it was deprecated because it wasn't as practical.
Why is this needed?
useState() is the main way to store a variable in React.
observable({...}) is the main way to create observable objects in Mobx.
And when using Mobx+React you'll often need the useState + observable combo, so it is nice to have a shorthand.
When is it not needed?
If you are using a class to create your store then you don't need the observable wrapper and you can simply do:
const myStore = useState(() => new MyStore())[0];
If you are using React class components you can save the store on a class field.
class MyComponent
{
myStore = new MyStore();
render() {...}
}
Bonus tip:
Why do we use useState for memorizing values instead of useMemo?
useMemo is meant to be used more like a cache for performance optimization, rather than for storing app state.

Vue.js global data access with v-model

I am working on a web app where users can work on a project. The structure of the app is as follows:
Component A (app)
Component B1-Bn (header, footer, main window etc., children of A)
Component C1 (Input area; with inputs for the user to work on the project, child of main window)
Component C2 (Output area; canvas which shows the result based on inputs from C1. In the future also a "graphical" input area that syncs with C1. Child of main window)
Component D1-Dn (Single parts of the input area like tables, advanced input components etc. Child of C1)
Now the project that the user is working on consists of an object stored in Component A. Component Dn needs to write to the object in Component A and also C2 in the future.
I can't get the v-model on input components Dn to work. I tried to pass the data from A down to C1 via props / v-bind and then in Dn, I v-model the prop from C1 (which originates from A) to the input-field. I also tried to use the sync modifier without sucess.
I seem to have a lack of understanding of the vue logic. I come from a desktop background where you just define the scope of variables.
I also found that other vue apprentices have the same understanding problem but somehow the answers I found where not sufficient.
I want a "global" variable that can be edited by every component and is linked to elements in the DOM. What would be the best way to achieve this?
Declare your variable at data when creating Vue Object in your root component (Component A) like
var app = new Vue({
data: function(){
return {
showSetting: {}
}
},
})
Now you can access this showSetting variable in any component like
app.showSetting;
//change it in any component
app.showSetting = {a:1,b:2};
//or append new value to object
Object.assign({d:3},app.showSetting);
Thanks for the answers so far. I guess both of them work. I found another solution because now I fully understand how data is passed in vue:Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child component will affect parent state. I will pass all data as arrays in the future, as I only want references. The only question that remains is why the programmer is not allowed to define by himself whether the data is passed by reference or not...
Source: Vue.js Guide

Vue: Forcing child component to react to changes in its prop (which is a dictionary)

I am currently generating a table which lists problems encountered during the selected test using a component generated with this code:
<tr is="entry" v-for="problem in problems" :key="problem.id" v-bind:foo="problem"></tr>
Each problem corresponds to an item whose relevant information is contained within the problem dictionary and referenced in the first few columns of the table. Since the same item can have multiple problems, the same item can appear in multiple rows of the table. Now, each row features some buttons which allow you to modify the underlying item so as to fix the problems.
Whenever I modify one of those underlying items I need to modify it in all the rows, which i do by calling a function in the parent component, but modifying the data inside of the dictionary does not seem to trigger any of my watches or computes inside of the child component, which currently looks something like this:
Vue.component('entry', {
props: ['foo'],
data: function(){
//does some computations
return data
},
watch:{
foo: function(){
this.recompute_entry()
},
},
methods:{
//various methods, including:
recompute_entry: function(){
//updates the data according to changes brought to the entry
},
},
});
I have attempted to include a different prop which i could bind to an entry in a list in my parent component but, besides being pretty clunky, that didn't end up working either, which makes me think I might've gotten something wrong with my component.
Ultimately, I have relied on the fact that v-for iterates through my list in an orderly fashion, which combined with the fact that I generate no other children in my parent component means that a child component would have the same index in my component's children array as it would in my problems array. Therefore I can use this:
this.$children[problem_index].recompute_entry();
Which kind of feels hack-ish and unreliable, but actually works, for once. Is there no alternative safer method to recalculate my child components based on changes made to their props? I really feel there has to be.
I probably would need to see the exact implementation but it sounds like you need to clone your dictionary to trigger the prop change, ie:
let newProblem = Object.assign({}, this.problem);
// change any nested property
newProblem.some.value = 1
// assign back the cloned and modified dictionary
this.problem = newProblem

How to properly select from multiple Redux-slices in mapStateToProps?

My Redux store is normalized, i.e. it's quite flat and each entity type has it's own slice.
Here is a simplified example of my Redux store:
drawings
1: {name:'D1', thumbnailId: 33}
2: {name:'D2', thumbnailId: 34}
thumbnails
33: {filePath: 'path to local file'}
34: {filePath: null (i.e. needs to be downloaded)}
The listview that shows the drawings whith respective thumbnail needs to be re-rendered when:
Changes in drawings-slice occurs, i.e. new, removed or updated drawings
Changes in any of the referenced thumbnails occurs, i.e. thumbnail 34 eventually gets downloaded (download is handled async by a Redux-Saga)
My current mapStateToProps is obviously flawed, as it does excessive selections from the Redux store. This happens before the actual view gets hold of its new props, so I cannot control this from shouldComponentUpdate.
There are several places in my app where I need a better solution for this.
Here is my flawed mapStateToProps (I'm using Immutable.js):
(state, ownProps) => {
const drawings = selectProjectDrawings(state, ownProps.projectId).map(drawing => {
const thumbnailFileGuid = drawing.get('thumbnailFileGuid');
if (!thumbnailFileGuid) return drawing;
const filePath = selectFile(state, thumbnailFileGuid).get(THUMBNAIL_FILE_PATH_FIELD);
return filePath ? drawing.set('_thumbnailFilePath', filePath) : drawing;
});
return {
drawings: drawings
};
}
Edit:
A really bad thing with my current solution is that I'm creating new drawing object by augmenting them with _thumbnailPath. This means that I cannot compare the object references in shouldComponentUpdate, as the reference is always altered.
Being able to compare object references is one of the main argument for not mutating the objects, but I have thrown away this opportunity with my flawed solution.
I would suggest placing this logic behind a selector. reselect is a selector memoization library which means that it will do a deep compare of the values for you and if the returned object and all the properties within stay the same, (including deep tree like structures) then it will return the original object.
React then will notice this is the same object instance (not a new one) and not re-render. It is OK if you recreate the drawing object within the selector, since you are not capturing this in any persistent state.
Side note: As your app grows, you will notice that all connected components will get mapStateToProps called even if it has nothing to do with it, therefore using a memoized selector really helps here. Otherwise your componentShouldUpdate gets complex real quick.

How to share one AJAX call between two stores?

I have two Sencha/ExtJS4 grids which use the exact same data (ie, same store.proxy.url), but each uses different filters, so each has its own separate store. The problem is I am making an unnecessary AJAX call to retrieve the extra copy to work with.
What is the recommended approach to make a single AJAX call and then share the data between two stores, for independent filtering?
potential solutions:
create two classes that extend the same store ?
use the same proxy instance ?
retrieve one store then cloning it ?
The Ext JS 4 framework seems to be built with the intention that each view receives its own store. As mentioned in other answers, your best option is to create a second store and copy all the records from one to the other.
function cloneStore(src, dest) {
var recs = src.getRange(); // returns array of records
dest.loadRecords(recs); // removes existing records before batch add
}
The exact implementation of that function may vary depending on how you need your data spread out. If each grid only needs a subset of the data to begin with, you can initialize a master store from your Ajax call, then create two sub-stores using filters directly on the store.data MixedCollection.
// Note: This function isn't exactly "good practice"
// Actual implementation may vary
function populateSubStores(master, storeA, storeB) {
var dataA = master.data.filter(/* filter criteria for store A */),
dataB = master.data.filter(/* filter criteria for store B */);
// dataA and dataB are MixedCollections of records
storeA.loadRecords(dataA.getRange());
storeB.loadRecords(dataB.getRange());
}
Or some variation thereof. This should be enough to get you started in the right direction.
If you're really gung-ho, you could create a new type of store that maintains separate MixedCollections representing filter states from different views, then return each filter state as a store with an identical interface to Ext.data.Store but with an implementation that operates on the "master" store's internal representation so that existing views can operate without overrides. But I don't recommend it.
You can create two instances of one store and then just copy data from one store to another using getRange() and add() methods. Creating two classes doesn't seem reasonable.