Accessing a getter from a parameterized getter - javascript

tl;dr
How to access other getters from a parameterized getter?
Normally, you can use this.myGetter; but a parameterized getter is implemented as an arrow function, wherein this is undefined.
What's the preferred way to handle this case in Pinia?
I'd like to create a parameterized getter (multiSum) in my Pinia store, which accesses another getter (sum).
Getters can be accessed via this, but that won't work from within an arrow function which is used to implemented a parameterized getter: multiSum crashes because this is undefined in the context of the nested arrow function.
getters: {
sum: (state) => state.a + state.b,
multiSum: (state) => (count) => this.sum * count // crash: this is undefined
}
In Vuex, parameterized getters can access other getters via a parameter instead of this, which also works in arrow functions. But afaik this API does not existing in Pinia.
I can work around this by capturing the store instance:
multiSum(state) {
const store = this
return (count) => store.sum * count
}
This works, but is pretty verbose. Is there a better (more framework-compliant) way to do this?

this can be undefined inside arrow function because it's not interchangeable with regular function and gets a context from parent scope.
The usage of this and state is explained in details in the documentation. Either this or state can be used, but this has better Typescript and thus IDE support.
In the first snippet, state is already available in function scope, there is no need to access this:
multiSum: (state) => (count) => state.sum * count
In the second snippet, const store = this isn't needed because a store is already this.
multiSum() {
return (count) => this.sum * count
}

Related

Passing parameters to Vuex getters from a Vuex action

I have a Vuex getter that I call from various components in my application. However, I have found a case were slightly more complex logic is required before calling the getter, so I am making use of a Vuex Action. How can I call the getter function with a parameter from my action?
I use constants for naming getters/mutations/actions, so my getter is defined as follows: [GETTER_NAME]: state => param => { return {/.../} }. In my Vuex action, I would like to call the getter as follows getters[GETTER_NAME](someParam). However, this does not seem to work (even though getters[GETTER_NAME] returns a function).
Calling the getter from a component works perfectly fine. I simply create computed function and use ...mapGetters({getterName: GETTER_NAME}). To call the getter with a parameter I just say getterName(someParam).
[GETTER_NAME]: state => param=> {
return {/.../}
},
[ACTION_NAME]: (context, param) => {
getters[GETTER_NAME](param)
? context.commit(MUTATION_X, param)
: context.commit(MUTATION_Y, param);
}
The getter gets called, however, it returns the function without passing in the parameter. Am I doing something wrong or am I misunderstanding the way getters work in Vuex?
You need to call like context.getters[GETTER_NAME](someParam) inside actions here.
[GETTER_NAME]: state => param=> {
return {/.../}
},
[ACTION_NAME]: (context, param) => {
context.getters[GETTER_NAME](param)
? context.commit(MUTATION_X, param)
: context.commit(MUTATION_Y, param);
}
In actions have injected parameters : dispatch, commit, getters and rootState. Therefore you can access getters like this:
ACTION_NAME: ({ commit, getters }, payload) => {
let MY_VARIABLE = getters.GETTER_NAME(payload)
console.log(MY_VARIABLE)
}
This works fine even if you try to access a getter from a different module.
Though you can use getters with context via context.getters the syntax gets a bit longish inside the action when using it this way.

Does selecting global state in generator make the function to be impure?

When i used to select the current state in a saga generator, does this make the function to be impure function, and by this it will affect the way in uni testing,
const currentState = state => state;
function* doLogin(){
const state = yield select(currentState);
//The rest of the code...
}
in the above example, i'm accessing a global object. And when i need to write unit test for this method, i need to simulate the same behavior, which is accessing a global object, and by this way it's considered to be impure function, is this correct assumption?

How do I disallow use of a required parameter in a ES7 function definition?

Question
I am writing a ES7 function that will be called by an API which defines the parameter order:
mapStateToProps(state, [ownProps]): stateProps
I need to use ownProps but not state. Is it possible to write the function definition so that the first parameter is either not specified or any attempt to read it or write it will result in an error ("fail fast")?
This doesn't work
This looks appealing, but of course _ is a valid variable name...:
function myMapStateToProps(_, ownProps) {
// ...
}
Some more context
My question is really the mirror of Skipping optional function parameters in JavaScript.
The context to my question is that I'm writing a pair of React components which implement a button that does something when it's clicked.
The inner component specifies the layout, including the button text, and the outer component makes the modal visible and is composed with the inner component using react-redux's connect function:
function MyButton({ handleOpen }) {
return (
<Button onClick={handleOpen} className={s['cta-button']}>
Button text that I want to configure
</Button>
);
}
const mapStateToProps = () => {
return {};
};
const mapDispatchToProps = (dispatch) => {
return {
handleOpen: () => dispatch(doSomeStuff()),
};
};
const MyButtonContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(MyButton);
export default MyButtonContainer;
Usage is:
<MyButtonContainer />
What I want to do is make the text displayed on the button configurable via a property passed to MyButtonContainer:
<MyButtonContainer text="Click me" />
By using the second parameter of the function used to map state to props (ownProps) I can access the text property, but I still don't need anything from the state so for clarity and to reduce the chance of causing bugs I want to write the mapStateToProps function so that the first parameter is unusable.
I am transpiling from ES7 so any standard ES is fine. If there are proposed extensions I might also be able to use those (thanks webpack).
Alternately, if there's a better way of doing this in React I'd be open to that solution too.
Why not put the button text in the redux store?
This is a technical option. I don't like it for several reasons:
- The button text is a property of the inner component, if I wasn't using a HOC to wrap it and provide the button functionality I'd just specify it directly as a property
- Putting it in the redux store is a lot of coding overhead - writing reducers, store initialisers, action creators, there must be a better and easier way!
- I want to display multiple buttons on the same rendered page, each with different text - no matter what I'm going to have to specify properties on the container class, having it be the text to display just seems cleanest
Using _ should be fine. It's intention is clear, and it shouldn't be used by accident by sensible developers.
You can use a higher-order function that drops the first argument:
function noState(fn) {
return (state, ...rest) => fn(...rest); // I'm assuming the `this` context doesn't matter
}
myMapStateToProps = noState(ownProps => {
…
});
If you want to disallow access to a variable, just shadow it with a more local one, and don't initialize it.
function func1(_, arg) {{
return 'This works: ' + arg;
let _;
}}
function func2(_, arg) {{
return 'This throws: ' + _; // ReferenceError
let _; // Prevents access to _
}}
console.log(func1(1,2));
console.log(func2(3,4));
The argument will still be accessible via arguments[0].

Is it safe to check object reference in componentWillReceiveProps?

I have a component connected to redux store and I have a code block like in this:
if (this.props.person !== nextProps.person) {
...
} else {
...
}
...
MyComponent.propTypes {
person: PropTypes.object.isRequired
}
Is it safe to check object reference in this way? Can I assume that in a reducer object reference will always change?
It is safe as long as your reducer is a pure function. To guarantee purity, those are the 3 most important things you should never do inside a reducer:
Mutate its arguments (use Object.assign or the object spread operator to avoid mutations on objects)
Perform side effects like API calls and routing transitions
Call non-pure functions, e.g. Date.now() or Math.random().
If your reducer satisfy all 3 conditions, any particular action dispatched that turned out to modify person property inside the state tree, will result in a new person object.
In that case, this.props.person and nextProps.person would be two different objects and that object reference check would be correct. However if a particular action dispatched didn't modify person property inside the state tree, this.props.person and nextProps.person would still be the same object
Consider a simple reducer:
function reducer(state, action) {
switch(action.type) {
...
default:
return state
}
}
This is a normal part of redux - if your reducer is called with an unknown action, it returns the same state. In this case, this.props.person === nextProps.person, because the object reference has not changed.
If your code (your reducer) changes the reference to person, the reference will have changed in your component as well.

Angular 2 bind to computed getter gives debug erros

I am working with Angular 2 and lodash.
I have a model with Relations and I have a getter like that:
get relationsPerType() {
return _(this.Relations)
.groupBy(p => p.Type)
.toPairs()
.map(p => ({
type: <MessageRelationType>p[0],
relations: <Relation[]>p[1]
}));
}
Binding the value of relationsPerType to an *ngFor directive
Edit: This is my binding:
<div *ngFor="#relationGroup of Message.relationsPerType">
...
</div>
Gives me the following error:
Expression 'Message.relationsPerType in MessagesDetailsComponent#60:8'
has changed after it was checked. Previous
which seems to be perfectly right as indeed this is computed each and every time it is called.
In any case, having "computed" variables like that I can't imagine how Angular 2 change detection could detect that relationsPerType has actually changed.
Something like marking the getter as immutable??
I suppose a better way would be:
a) To store the computed getter values inside a property right from start
b) To make the parent object Immutable so as for Angular to not track changes on properties
Is there a better way to do this?
After some digging, I found a better way using decorators, than a) and b) has to offer, as:
a) I don't want to loose the "lazy" computations that getter functions provide and make everything a property
b) Immutable IS a viable solution but not applicable to my case
So, coming from C# and lots of Aspect Oriented programming (see PostSharp), I finally managed to create a cached property getter decorator function that is evaluated only once per object:
function cachedProperty(target: any, key: string, descriptor: PropertyDescriptor) {
let originalGetter = descriptor.get;
descriptor.get = function () {
let cachSetted = this[`__cachedsetted${key}`];
if (typeof cachSetted !== 'undefined') {
return this[`__cached${key}`];
}
this[`__cachedsetted${key}`] = true;
return this[`__cached${key}`] = originalGetter.call(this);
}
}
after that all that needs to change is decorate the getter with the #cachedProperty decorator, like so:
#cachedProperty
get relationsPerType() {
return _(this.Relations)
.groupBy(p => p.Type)
.toPairs()
.map(p => ({
type: <MessageRelationType>p[0],
relations: <Relation[]>p[1]
})).value();
}
Using this decorator the object will change only once so as for the Angular 2 dependency injection not to complaint. Also, I don't loose the "lazy" evaluations, and I don't add helper properties that changes my schema.
One must of course handle the case where he wants to invalidate the cache if this becomes stale. That would require to remove the
`__cachedsetted${key}`
property from this.
You should cache the value and bind to the cached value instead or create an observable that emits new events when data changes.
If you bind to {{relationsPerType}} a new collection is created every time Angular checks if a change happend and Angular sees this as change because it gets two different instances, even though they might contain the same data.
calcRelationsPerType() {
this relationsPerType = _(this.Relations)
.groupBy(p => p.Type)
.toPairs()
.map(p => ({
type: <MessageRelationType>p[0],
relations: <Relation[]>p[1]
}));
}
then binding like this should work fine:
<div *ngFor="#relationGroup of Message.relationsPerType">
...
</div>

Categories

Resources