I rendered a list of modal/popups and control them by using state.
renderGenericOKModal({ title, message, visibleStateVar }) {
return (
<PromptModal
alertTitle={title}
alertMessage={message}
isVisible={this.state[visibleStateVar]}
onRightButtonPress={() =>
this.setState({ [visibleStateVar]: false });
}
rightButtonLabel={strings.labelOK}
/>
);
}
I see some comments on medium saying that the code above is not optimized because it creates a lot of unnecessarily similar function for onRightButtonPress. So I tried to do something as below but not sure if it actually make any differences?
genericOKButtonOnPress = ({ visibleStateVar }) => {
this.setState({ [visibleStateVar]: false });
};
onRightButtonPress={() =>
this.genericOKButtonOnPress({ visibleStateVar })
}
But the code above is still creating each individual anonymous function object which looks the same as my original code right?
UPDATES:
onRightButtonPress={this.genericOKButtonOnPress.bind(null, {
visibleStateVar
})}
Using bind should be beneficial for my case right?
Define the onRightButtonPress using arrow function without anonymous function.
onRightButtonPress = () => this.genericOKButtonOnPress({ visibleStateVar }) //inline
or
onRightButtonPress = () => {
this.genericOKButtonOnPress({ visibleStateVar })
} //using block
PromptModal should be
<PromptModal
alertTitle={title}
alertMessage={message}
isVisible={this.state[visibleStateVar]}
onRightButtonPress=this.onRightButtonPress
rightButtonLabel={strings.labelOK}
/>
Related
I'm having lots of elements on which #mouseenter set a value to true and #mouseleave sets it to false. Basically what I need is a way to set a reactive variable to true if the mouse hovers the element.
I've been trying to figure out how to write such custom directive from the docs but it only mentions how to use .focus() js function on an element. Which js functions would be used for said directive?
Something like:
const vHover = {
mounted: (el) => {
el.addEventListener('mouseenter', state.hover=true)
el.addEventListener('mouseleave', state.hover=false)
}
}
I think you could do something like:
app.directive('hover', {
created(el, binding) {
const callback = binding.value
el.onmouseenter = () => callback(true)
el.onmouseleave = () => callback(false)
},
unmounted(el) {
el.onmouseenter = null
el.onmouseleave = null
}
})
Template:
<button v-hover="onHoverChange">Example</button>
Methods:
onHoverChange(isHovered) {
console.log(isHovered)
}
I believe this is not the intended use of directives. The value of the state cannot be mutated within the directive. You can pass the variable through the binding, but you cannot update it.
binding: an object containing the following properties.
value: The value passed to the directive. For example in v-my-directive="1 + 1", the value would be 2.
oldValue: The previous value, only available in beforeUpdate and updated. It is available whether or not the value has changed.
so if you do el.addEventListener('mouseenter', binding.hover=true), as you may have noticed, it will not update the state.
However, if we use the internals (PSA: though not recommended since they could potentially change at any time), you could get instance using the vnode, and use the binding.arg to denote which Proxy (state)
so you could get the reactive variable with vnode.el.__vueParentComponent.data[binding.arg]
<script>
export default {
data(){
return {
state: { hover:false }
}
},
directives: {
hover: {
mounted(el, binding, vnode) {
el.addEventListener('mouseenter', () => {
vnode.el.__vueParentComponent.data[binding.arg].hover = true
})
el.addEventListener('mouseleave', () => {
vnode.el.__vueParentComponent.data[binding.arg].hover = false
})
},
}
}
}
</script>
<template>
<h1 v-hover:state="state">HOVER {{ state }}</h1>
</template>
SFC playground link
of course you might want to add the unmounted and even consider adding mouseleave dynamically only when mouseenter fires
This is how it can be done inside the component:
const vHover = {
mounted: (el) => {
el.addEventListener('mouseenter', () => {state.hover=true})
el.addEventListener('mouseleave', () => {state.hover=false})
},
unmount: (el) => {
el.removeEventListener('mouseenter', () => {state.hover=true})
el.removeEventListener('mouseleave', () => {state.hover=false})
}
}
I have the following usage of rxjs streams:
ngOnInit() {
combineLatest(
this.eventsService.subjectSearchDistribution.pipe(
tap((querySearch) => {
this.paginationService.setQuery(querySearch);
this.paginationService.reset();
}),
),
this.eventsService.subjectSortingDistribution.pipe(
tap((sortedList: ListItem[]) => {
this.paginationService.setSortBy(getSortingString(sortedList));
}),
),
this.eventsService.subjectFilterDistribution.pipe(
tap((filterUrl) => {
const page = 1;
this.paginationService.setFilterBy(filterUrl);
this.paginationService.setCurrentPage(page);
this.paginationService.calculateOffsetLimit(page);
}),
),
this.eventsService.subjectFilterDistributionReset.pipe(tap(() => this.paginationService.reset())),
).subscribe(() => {
this.loadPage();
});
}
Problem is I need to handle only one case, onle one stream and dont call others, as result call this.loadPage();.
Now when I send message to this.eventsService.subjectSearchDistribution, this.eventsService.subjectSortingDistribution, this.eventsService.subjectFilterDistribution.
I see that calling of this.loadPage(); increases from fist time +1 each event.
SO, ONLY one observer can be active, not all torgether.
How to fix it?
It seems the reason your loadPage method is called twice due to your event listeners, but without sharing the code for those methods I cannot confirm that issue. The simplest way to fix your double call of the loadPage method would be this:
class A {
constructor() {
this.pageLoadCalled = false;
this.loadPage();
this.events.filter.listen().subscribe((res) => this.loadPage());
this.events.search.listen().subscribe((res) => this.loadPage());
}
loadPage() {
if (this.pageLoadCalled) {
// Exit early (will not call anything below the return)
return;
}
// Mark this method as being called
this.pageLoadCalled = true;
return new Promise((resolve) => {
// do stuff
resolve();
});
}
}
If you want to call loadPage only once, don't execute it when events.filter and events.search trigger:
class A {
constructor() {
// Call pageLoad in the constructor only once
this.loadPage();
// Remove call to pageLoad when events fire.
// this.events.filter.listen().subscribe((res) => this.loadPage());
// this.events.search.listen().subscribe((res) => this.loadPage());
}
}
I solved this using rxjs:
ngOnInit() {
combineLatest(
this.eventsService.subjectSearchDistribution.pipe(
tap((querySearch) => {
this.paginationService.setQuery(querySearch);
this.paginationService.reset();
}),
),
this.eventsService.subjectSortingDistribution.pipe(
tap((sortedList: ListItem[]) => {
this.paginationService.setSortBy(getSortingString(sortedList));
}),
),
this.eventsService.subjectFilterDistribution.pipe(
tap((filterUrl) => {
const page = 1;
this.paginationService.setFilterBy(filterUrl);
this.paginationService.setCurrentPage(page);
this.paginationService.calculateOffsetLimit(page);
}),
),
this.eventsService.subjectFilterDistributionReset.pipe(tap(() => this.paginationService.reset())),
)
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.loadPage();
});
}
MethodToBeTested() {
this.serviceA.methodA1().subscribe((response) => {
if (response.Success) {
this.serviceA.methodA2().subscribe((res) => {
this.serviceB.methodB1();
})
}
});
}
Here is the scenario.
Things to test:
serviceA.methodA1(). was called.
if response.Success then check if serviceA.methodA2() was called
check if serviceB.methodB1() was called when serviceA.methodA2() received value.
first, one is easy to test.
let spy = spyOn(serviceA, 'methodA1');
expect(spy).toHaveBeenCalled();
But does one test 2 and 3?
let spy= spyOn(serviceA, 'methodA1').and.returnValue({subscribe: () => {success:true}});
subject.MethodToBeTested();
something like that?
Alright, so I figured out what I am looking for is callFake
it('should test inside of subscribe', () => {
let spy = spyOn(serviceA, 'methodA1').and.callFake(() => {
return of({ success: true });
});
let spy2 = spyOn(serviceA, 'methodA2').and.callFake(() => {
return of({ success: true });
});
let spy3 = spyOn(serviceB, 'methodB1').and.returnValue(of({ success: true }));
subject.MethodToBeTested();
expect(spy3).toHaveBeenCalled();
});
I learned that returnValue won't actually execute the inside of the subscribe while callFake will with the data you provide inside it.
It would be better to not use a nested subscribe.
Something like this could be a sollution:
let $obs1 = this.serviceA.methodA1().pipe(share());
let $obs2 = $obs1.pipe(switchMap(x => this.serviceA.methodA2()));
$obs1.subsribe(logic1 here...);
$obs2.subsribe(logic2 here...);
Given the code below, how can I pass id to the applySaveAsync function?
var then = _.curry(function (f, thenable) {
return thenable.then(f);
});
var validateAsync = _.flow(
function () { return _(someCondition).showError(ERROR_01).value(); },
then(function () { return _(anotherCondition).showError(ERROR_02).value(); })
);
var save = _.flow(
validateAsync,
then(applySaveAsync),
then(saveCompleted)
);
function applySaveAsync(id) {
// Saving...
}
save(22); // Calling save function with some id.
I can get the id on the validateAsync function, but I cannot return it back since validateAsync should return a promise.
Any way to achieve that?
The simplest choice would be not to use _.flow for the definition of validateAsync.
Since validateAsync does not take parameters nor has a result, you should just change the definition of save to not use _.flow:
function save(id) {
return validateAsync()
.then(function(){ return applySaveAsync(id) })
.then(saveCompleted)
}
We could also change validateAsync to pass through the id:
function validateAsync(id) {
return _(someCondition).showError(ERROR_01).value()
.then(function () { return _(anotherCondition).showError(ERROR_02).value(); })
.then(_.constant(id));
}
and even do that while still using _.flow
var validateAsync = _.flow(
function(id) { return _(someCondition).showError(ERROR_01).value().then(_.constant(id)); },
then(function(id) { return _(anotherCondition).showError(ERROR_02).value().then(_.constant(id)); })
);
but I would advise against that since validateAsync is not supposed to be a function that does takes parameters.
Let's write a wrapper function for such instead to let us do the pass-around in a functional way:
function pass(fn) {
return function(id) {
return fn().then(function() {
return id;
});
}
}
(if you prefer, you can try to compose that from then, _.constant and more)
so that one can write
var save = _.flow(
wrap(validateAsync),
then(applySaveAsync),
then(saveCompleted)
);
I found this package useful for you. In Async cases, you can use this package.
Although flow is one of the best implementations for declarative programming, it doesn't support modern JS programming style.
import { Conductor } from '#puzzleio/conductor';
const conductor = Conductor.createDefault();
const myAsyncWorkflow = conductor
.add(validateAsync)
.if({
check: item => item.isValid === true,
handler: item => console.log('Item is valid')
},
{
// else block
handler: item => console.log('Validation failed')
});
myAsyncWorkflow.run(obj)
.then(() => console.log('Successfully validated'))
.catch(console.error);
I'm employing the suggestion from #gaearon to setup a listener on my redux store. I'm using this format:
function observeStore(store, select, onChange) {
let currentState;
if (!Function.prototype.isPrototypeOf(select)) {
select = (state) => state;
}
function handleChange() {
let nextState = select(store.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState);
}
}
let unsubscribe = store.subscribe(handleChange);
handleChange();
return unsubscribe;
}
I'm using this in an onEnter handler for a react-router route:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
const disposeRouteHandler = observeStore(store, null, (state) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
// I'm done: how do I dispose the store subscription???
}
});
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
Basically this helps gate the progression of the router while actions are finishing dispatching (async).
My problem is that I can't figure out where to call disposeRouteHandler(). If I call it right after the definition, my onChange function never gets a chance to do it's thing, and I can't put it inside the onChange function because it's not defined yet.
Appears to me to be a chicken-egg problem. Would really appreciate any help/guidance/insight.
How about:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
let shouldDispose = false;
const disposeRouteHandler = observeStore(store, null, (state) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
if (disposeRouteHandler) {
disposeRouteHandler();
} else {
shouldDispose = true;
}
}
});
if (shouldDispose) {
disposeRouteHandler();
}
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
Even though using the observable pattern leads to some buy-in, you can work around any difficulties with normal js code. Alternatively you can modify your observable to suit your needs better.
For instance:
function observeStore(store, select, onChange) {
let currentState, unsubscribe;
if (!Function.prototype.isPrototypeOf(select)) {
select = (state) => state;
}
function handleChange() {
let nextState = select(store.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState, unsubscribe);
}
}
unsubscribe = store.subscribe(handleChange);
handleChange();
return unsubscribe;
}
and
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
const disposeRouteHandler = observeStore(store, null, (state, disposeRouteHandler) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
disposeRouteHandler();
}
}
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
It does add a strange argument to onChange but it's just one of many ways to do it.
The core problem is that handleChange gets called synchronously immediately when nothing has changed yet and asynchronously later. It's known as Zalgo.
Inspired by the suggestion from #DDS, I came up with the following alteration to the other pattern mentioned in #gaearon's comment:
export function toObservable(store) {
return {
subscribe({ onNext }) {
let dispose = this.dispose = store.subscribe(() => {
onNext.bind(this)(store.getState())
});
onNext.bind(this)(store.getState());
return { dispose };
},
dispose: function() {},
}
}
This allows me to invoke like:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
toObservable(store).subscribe({
onNext: function onNext(state) {
const conditions = [/* many conditions */];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
this.dispose(); // remove the store subscription
}
},
});
store.dispatch(/* action */);
};
};
The key difference is that I'm passing a regular function in for onNext so as not to interfere with my bind(this) in toObservable; I couldn't figure out how to force the binding to use the context I wanted.
This solution avoids
add[ing] a strange argument to onChange
... and in my opinion also conveys a bit more intent: this.dispose() is called from within onNext, so it kinda reads like onNext.dispose(), which is exactly what I want to do.