Get parent data from a child - mobx-state-tree

What way is better? I think than first way is better. Import parent in child looks weird to me, but maybe I'm wrong.
RootStore:
export const RootStore = types
.model('RootStore', {
store1: types.optional(Store1, {}),
store2: types.optional(Store2, {}),
store3: types.optional(Store3, {}),
store3: types.optional(Store4, {}),
name: 'name'
})
export const rootStore = RootStore.create()
First way:
export const Store1 = types
.model('Store1', {
some: ''
})
.views(self => ({
get rootStore() {
return getParent(self)
},
get name() {
return self.rootStore.name
}
}))
Second way:
import { rootStore } from './rootStore'
export const Store1 = types
.model('Store1', {
some: ''
})
.views(self => ({
get name() {
return rootStore.name
}
}))

All answers to this question are likely to be opinionated..
If you are going to do this, I think the first way is better. Just because it means the child does not need to know anything about it's parent other than that it exposes a name property.
That being said, I'm really not a big fan of either approaches.
Whether you use getParent or a closure, this encourages a coupling of the two models. This results in decreased modularity and harder testing since every Store1 must be a child of a RootStore to function properly.
I think a better approach would be to remove the dependency between child->parent. However, if you are purposefully making use of the tree structure that MST provides, my suggestion might be better in theory than practice.
The simplest approach to removing the dependency is to have the caller of Store1's actions/views pass whatever data is needed in as parameters. Once again, this does not always make sense in practice.

If all you need is access the root node in the tree, then there's a dedicated helper function specifically for that case - getRoot(self).
Given an object in a model tree, returns the root object of that tree.

Related

How to create a computed based on another computed from composable

I'm learning composition API doing a simple todo app but then with multiple swim-lanes (todo, doing, done).
in useTasks.js composable I create "global" sourceTasks reactive, that later gets filled with data pulled from an API. Then it is reduced in tasks computed property, like this:
// useTasks.js
const sourceTasks = reactive({
list: []
});
export default function useTasks() {
const tasks = computed(() => {
return sourceTasks.list.reduce(divideIntoSwimLanes, [])
});
...
return {
tasks,
loadTasks,
createTask
}
}
Nothing too complicated.
Then I've got this SwimLane component, that well... uses the tasks :)
// SwimLane.vue - setup
async setup(props) {
const { status } = toRefs(props);
const { tasks, createTask } = useTasks();
return {
tasks,
label,
createTask
}
}
// SwimLane.vue - template
<single-task class="m-3" v-for="task in tasks[status]" :title="task.title" :id="task.id"/>
This works, but I don't find it elegant. I would prefer to create a new computed inside of SwimLane's setup, that holds the value of tasks for the given SwimLane. Putting it in the template obscures the logic.
I would expect this to work, but it does not, I think it loses the reactivity but I cant wrap my head around why:
// SwimLane.vue - alternative setup
const currentContextTasks = computed(() => {
return tasks.value[status]
});
return {
currentContextTasks
}
The problem feels a bit ridiculous, but my main concern is that I have misunderstood some core concept, hence this lengthy post.
This is like a biggest blunders ever. So right, the post was really helpful as a form of talking to the rubber duck.
What I did forgot to do is use the value of the status. Since it is a ref, I had to use it as follows:
const currentContextTasks = computed(() => {
return tasks.value[status.value] // 'status.value' and not just 'status'
});

Reducer for deep state

I have an initial state which looks like this (simplified for the purpose of this question):
export default {
anObject: {
parameters: {
param1:'Foo',
param2:'Bar'
},
someOtherProperty:'value'
}
};
And I have a reducer for anObject part of which deals with changes to parameter. I have an action which passed the id of the parameter to change, along with the newValue for that parameter. The reducer (again, very slightly simplified) looks like this:
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function anObjectReducer(state = initialState.anObject, action){
switch(action.type){
case types.UPDATE_PARAMETER:
return Object.assign(
{},
state,
{
parameters:Object.assign(
{},
state.parameters,
{ [action.id]: action.newValue })
});
default:
return state;
}
}
This reducer looks wrong to me. I assumed I would be able to just do it like this:
case types.UPDATE_PARAMETER:
return Object.assign({},state,{parameters:{[action.id]:action.newValue}});
But this seems to wipe out all the other parameters and just update the single one being changed. Am I missing something obvious about how to structure my reducer?
In case it's relevant this is how I set up my root reducer:
import { combineReducers } from 'redux';
import anObject from './anObjectReducer';
export default combineReducers({
anObject
});
I thought there might be a way to compose reducers for the individual parts of each object - ie separately for parameters and someOtherProperty part of anObject in my example?
The reason why it wipes out other parameters is because you don't pass the previous values in the Object.assign.
You should have done that:
return Object.assign({}, state, {
parameters: Object.assign({}, { [action.id]: action.newValue }),
});
Or with the ES6 spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
return {
...state,
parameters: {
...state.parameters,
[action.id]: action.newValue,
}
}
You can:
restructure your reducers: you may use combineReducers not for store's root only. This way store stays the same as well as actions but reducers for nested object become lightweight
restructure your state(and reducers) to have it as flat as possible. it'd be more efforts needed but typically it also make easier to fetch data in mapStateToProps. normalizr should help to make transition easier by encapsulating integration with existing API that
requires specific data structure
use immer to write code like you're mutating state. This is definitely bad idea if you are learning React, but I'd consider using it on small real projects

how to ignore ordering of reselect selectors when composing selectors

As I compose more selectors together, I'm finding that I'm reordering where the selectors are defined. For example,
export const selectNav = state => state.nav;
export const selectPage = state => state.page;
export const selectNavAndPage = createSelector([
selectNav,
selectPage,
], (nav, page) => {
});
export const selectFoo = state => state.foo;
export const selectNavAndPageAndFoo = createSelector([
selectNavAndPage,
selectFoo,
], (navAndPage, foo) => {
});
This is a simple example, but I could not define selectNavAndPage below selectNavAndPageAndFoo. As more selectors get composed and selectors of selectors get composed, then I need to make sure all the sub-selectors are defined at the top before I use them.
Is there some way to create these selectors such that ordering doesn't matter?
I was worried about the same problem and I created this npm module define-selectors. This is a module that delays the definition of the selector to solve the ordering of selector definition problem and adds other features to it. It has not been stable yet, but I will use it on my project to make it stable and be improved.
For more information please go to github page and read the README and source files.
I'm pretty sure this is just related to how the ES6 const keyword works. With const, variables do not exist until that line, so if you want to reference a const variable, you need to write that code after the variable declaration. With var, all variables are hosted to the top of the scope.
So, either use var so that you can reference things out of order, or continue using const and define each function in the correct order for usage and references.
If you don't mind a little extra typing, here is another approach which requires defining a 'cms' utility function that wraps the createSelector function and takes advantage of function hoisting:
import {createSelector} from 'reselect';
// create memoized selector
function cms(ctx, ...args) {
if (!ctx.selector) ctx.selector = createSelector(...args);
return ctx.selector;
}
// define the selectors out of order...
export function getBaz(state) {
return cms(
getBaz // the function itself as context
, getBar
, bar => bar.baz
)(state);
}
export function getBar(state) {
return cms(
getBar
, getFoo
, foo => foo.bar
)(state);
}
export function getFoo(state) {
return state.foo;
}
This is not as elegant as simply defining the selectors in order, but maybe someone else can take this idea and improve on it.

Rxjs Observable.take(1) vs Subscription.unsubscribe()

Is there any differences between
Observable.pipe(take(1)).subscribe(...)
vs
const subscription = Observable.subscribe(() => {
// Do something, then
subscription.unsubscribe()
})
The take(1) approach has a number of advantages over subscribe:
Code readability (and elegance).
The second approach requires that you hold and manage extra variables.
The second approach will not invoke the complete handler. This is because .take(1) actually create a new observable which potentially yields a single item and completes.
The second approach will work for the trivial case of taking a single element, but if you need to take more then 1, take(4) will stay simple while the second approach will become hard to code.
The 3rd item is the rxjs related one, the others relate to coding style.
Have a look at a sample here.
In Angular2, I find myself using both paradigms.
The first makes the most sense inside of a method, where as the second is better used in a constructor, with a cleanup in the deconstructor.
doThing(){
this.store.select('thing').pipe(take(1))
.subscribe(item => {
otherMethod(item)
});
}
vs
class SomeClass{
public val;
private sub;
constructor(){
this.sub = this.store.select('thing')
.subscribe(item => {
this.val = item
});
}
ngDestroy() {
this.sub.unsubscribe()
}
}

When to use asObservable() in rxjs?

I am wondering what is the use of asObservable:
As per docs:
An observable sequence that hides the identity of the
source sequence.
But why would you need to hide the sequence?
When to use Subject.prototype.asObservable()
The purpose of this is to prevent leaking the "observer side" of the Subject out of an API. Basically to prevent a leaky abstraction when you don't want people to be able to "next" into the resulting observable.
Example
(NOTE: This really isn't how you should make a data source like this into an Observable, instead you should use the new Observable constructor, See below).
const myAPI = {
getData: () => {
const subject = new Subject();
const source = new SomeWeirdDataSource();
source.onMessage = (data) => subject.next({ type: 'message', data });
source.onOtherMessage = (data) => subject.next({ type: 'othermessage', data });
return subject.asObservable();
}
};
Now when someone gets the observable result from myAPI.getData() they can't next values in to the result:
const result = myAPI.getData();
result.next('LOL hax!'); // throws an error because `next` doesn't exist
You should usually be using new Observable(), though
In the example above, we're probably creating something we didn't mean to. For one, getData() isn't lazy like most observables, it's going to create the underlying data source SomeWeirdDataSource (and presumably some side effects) immediately. This also means if you retry or repeat the resulting observable, it's not going to work like you think it will.
It's better to encapsulate the creation of your data source within your observable like so:
const myAPI = {
getData: () => return new Observable(subscriber => {
const source = new SomeWeirdDataSource();
source.onMessage = (data) => subscriber.next({ type: 'message', data });
source.onOtherMessage = (data) => subscriber.next({ type: 'othermessage', data });
return () => {
// Even better, now we can tear down the data source for cancellation!
source.destroy();
};
});
}
With the code above, any behavior, including making it "not lazy" can be composed on top of the observable using RxJS's existing operators.
A Subject can act both as an observer and an observable.
An Obervable has 2 methods.
subscribe
unsubscribe
Whenever you subscribe to an observable, you get an observer which has next, error and complete methods on it.
You'd need to hide the sequence because you don't want the stream source to be publicly available in every component. You can refer to #BenLesh's example, for the same.
P.S. : When I first-time came through Reactive Javascript, I was not able to understand asObservable. Because I had to make sure I understand the basics clearly and then go for asObservable. :)
In addition to this answer I would mention that in my opinion it depends on the language in use.
For untyped (or weakly typed) languages like JavaScript it might make sense to conceal the source object from the caller by creating a delegate object like asObservable() method does. Although if you think about it it won't prevent a caller from doing observable.source.next(...). So this technique doesn't prevent the Subject API from leaking, but it indeed makes it more hidden form the caller.
On the other hand, for strongly typed languages like TypeScript the method asObservable() doesn't seem to make much sense (if any).
Statically typed languages solve the API leakage problem by simply utilizing the type system (e.g. interfaces). For example, if your getData() method is defined as returning Observable<T> then you can safely return the original Subject, and the caller will get a compilation error if attempting to call getData().next() on it.
Think about this modified example:
let myAPI: { getData: () => Observable<any> }
myAPI = {
getData: () => {
const subject = new Subject()
// ... stuff ...
return subject
}
}
myAPI.getData().next() // <--- error TS2339: Property 'next' does not exist on type 'Observable<any>'
Of course, since it all compiles to JavaScript in the end of the day there might still be cases when you want to create a delegate. But my point is that the room for those cases is much smaller then when using vanilla JavaScript , and probably in majority of cases you don't need that method.
(Typescript Only) Use Types Instead of asObservable()
I like what Alex Vayda is saying about using types instead, so I'm going to add some additional information to clarify.
If you use asObservable(), then you are running the following code.
/**
* Creates a new Observable with this Subject as the source. You can do this
* to create customize Observer-side logic of the Subject and conceal it from
* code that uses the Observable.
* #return {Observable} Observable that the Subject casts to
*/
asObservable(): Observable<T> {
const observable = new Observable<T>();
(<any>observable).source = this;
return observable;
}
This is useful for Javascript, but not needed in Typescript. I'll explain why below.
Example
export class ExampleViewModel {
// I don't want the outside codeworld to be able to set this INPUT
// so I'm going to make it private. This means it's scoped to this class
// and only this class can set it.
private _exampleData = new BehaviorSubject<ExampleData>(null);
// I do however want the outside codeworld to be able to listen to
// this data source as an OUTPUT. Therefore, I make it public so that
// any code that has reference to this class can listen to this data
// source, but can't write to it because of a type cast.
// So I write this
public exampleData$ = this._exampleData as Observable<ExampleData>;
// and not this
// public exampleData$ = this._exampleData.asObservable();
}
Both do the samething, but one doesn't add additional code calls or memory allocation to your program.
❌this._exampleData.asObservable();❌
Requires additional memory allocation and computation at runtime.
✅this._exampleData as Observable<ExampleData>;✅
Handled by the type system and will NOT add additional code or memory allocation at runtime.
Conclusion
If your colleagues try this, referenceToExampleViewModel.exampleData$.next(new ExampleData());, then it will be caught at compile time and the type system won't let them, because exampleData$ has been casted to Observable<ExampleData> and is no longer of type BehaviorSubject<ExampleData>, but they will be able to listen (.subscribe()) to that source of data or extend it (.pipe()).
This is useful when you only want a particular service or class to be setting the source of information. It also helps with separating the input from the output, which makes debugging easier.

Resources