I have an effect that needs to return two actions. First, there is some logic involved before returning the first action. The second action is supposed to be dispatched after the first action, because it gets the data from the first action's state.
I have something like this:
return Observable.combineLatest(dataRequests).pipe(
map(res => {
const rows = *some logic done here*
};
return {
type: types.SET_DATA,
payload: rows;
}
})
merge(observableOf({
type: types.SAVE_DATA
}))
)
The problem is that SAVE_DATA is getting dispatched before SET_DATA, therefore it's not able to get the data from the state. How can I modify this so SAVE_DATA is always dispatched after SET_DATA?
fixed by replacing "merge" with "concat"
Related
My use case comprises of dispatching two actions from a recursive function (if else construct ) the if part which adds a row in an array ( which is a state of my app) and the else part adds another row and needs to access the length of the array before and call the function itself. What I see here is the length of the array remains same after the first action is being dispatched and thus the call to itself doesn't get the actual value of the length .
My assumption is you are trying to do the second check/call after the first within the component. The component must wait for the new props on the next render. You should move your logic into your action. This is just a guess without more details to the question. Here is an example:
const myAction = (stuff) => {
return (dispatch, getState) => {
let oldLength = getState().myState.stuff.length
dispatch(doStuffToStuff(stuff))
let newLength = getState().myState.stuff.length
dispatch(moreStuffToLength(newLength))
}
}
Thanks the issue was resolved .After every dispatch if the state changes it is required to access the new state by ysing getState() I wasn't doing that .
For example we have reducer photos, which handles array of photos via actions ADD_PHOTO and REMOVE_PHOTO. And if we have arrays users and posts, they both have field for array of photos.
So, in order to avoid code duplicates I'm going to do the following:
Create reducer user = combineReducers(..., photos, ...)
Create actionCreator updateUser
const updateUser = (id, subAction) => ({
type: UPDATE_USER,
payload: {
id,
subAction
}
})
Create reducer users (Here I'm using Immutable.js)
function users(state = List(), action) {
switch (action.type) {
//...
case UPDATE_USER:
const { id, subAction } = action.payload
const index = state.findIndex(user => user.id == id)
return state.updateIn(
[index, 'photos'],
state => photos(state, subAction)
)
break
//...
default:
return state
}
}
And then I'm going to use all of it like this:
dispatch(updateUser(id, addPhoto(url)))
Is this a correct solution of my problem?
Why not simply dispatch both in the place where this is initiated by the user?
dispatch(updateUser(id));
dispatch(addPhoto(url));
I haven't come across this pattern you're applying before. It seems a bit unusual that one reducer is responsible for reducing state of another. Creates a dependency between them that doesn't feel very pure. I'm not even sure one reducer should be able to/can see the state of another.
So no idea about "correct", but I'd say it's not ideal to do it your way. I'd try dispatching both sequentially or maybe in a sort of meta-action that takes care of nested updates and dispatches actions to multiple reducers.
I have a Meteor Helper that does a GET request and am supposed to get response back and pass it back to the Template, but its now showing up the front end. When I log it to console, it shows the value corerctly, for the life of mine I can't get this to output to the actual template.
Here is my helper:
UI.registerHelper('getDistance', function(formatted_address) {
HTTP.call( 'GET', 'https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=Washington,DC&destinations='+formatted_address+'&key=MYKEY', {}, function( error, response ) {
if ( error ) {
console.log( error );
} else {
var distanceMiles = response.data.rows[0].elements[0].distance.text;
console.log(response.data.rows[0].elements[0].distance.text);
return distanceMiles;
}
});
});
In my template I pass have the following:
{{getDistance formatted_address}}
Again, this works fine and shows exactly what I need in the console, but not in the template.
Any ideas what I'm doing wrong?
I posted an article on TMC recently that you may find useful for such a pattern. In that article the problem involves executing an expensive function for each item in a list. As others have pointed out, doing asynchronous calls in a helper is not good practice.
In your case, make a local collection called Distances. If you wish, you can use your document _id to align it with your collection.
const Distances = new Mongo.collection(); // only declare this on the client
Then setup a function that either lazily computes the distance or returns it immediately if it's already been computed:
function lazyDistance(formatted_address){
let doc = Distances.findOne({ formatted_address: formatted_address });
if ( doc ){
return doc.distanceMiles;
} else {
let url = 'https://maps.googleapis.com/maps/api/distancematrix/json';
url += '?units=imperial&origins=Washington,DC&key=MYKEY&destinations=';
url += formatted_address;
HTTP.call('GET',url,{},(error,response )=>{
if ( error ) {
console.log( error );
} else {
Distances.insert({
formatted_address: formatted_address,
distanceMiles: response.data.rows[0].elements[0].distance.text
});
}
});
}
});
Now you can have a helper that just returns a cached value from that local collection:
UI.registerHelper('getDistance',formatted_address=>{
return lazyDistance(formatted_address);
});
You could also do this based on an _id instead of an address string of course. There's a tacit assumption above that formatted_address is unique.
It's Meteor's reactivity that really makes this work. The first time the helper is called the distance will be null but as it gets computed asynchronously the helper will automagically update the value.
best practice is not to do an async call in a helper. think of the #each and the helper as a way for the view to simply show the results of a prior calculation, not to get started on doing the calculation. remember that a helper might be called multiple times for a single item.
instead, in the onCreated() of your template, start the work of getting the data you need and doing your calculations. store those results in a reactive var, or reactive array. then your helper should do nothing more than look up the previously calculated results. further, should that helper be called more times than you expect, you don't have to worry about all those additional async calls being made.
The result does not show up because HTTP.call is an async function.
Use a reactiveVar in your case.
Depending on how is the formated_address param updated you can trigger the getDistance with a tracker autorun.
Regs
Yann
I have a Redux app that is displaying a list of Properties based on a set of Filters (user input).
Quick description of my state:
filters – Filters values object...
properties – Repo of all properties available on page
visibleProperties – List of properties with current filters applied
The problem is when I dispatch & set a new filter value, I need to filter properties based on filters new state and to store the result in visibleProperties.
So I came up with this solution:
export function setBedroomFilter (value) {
return (dispatch, getState) => {
// 'SET_FILTER' action
dispatch(setFilter('bedroom', parseInt(value)))
// New state
const { filters, properties } = getState()
// 'FILTER_PROPERTIES' action (Depending on new state)
dispatch(filterProperties(properties, filters))
}
}
And visibleProperties reducer can do its work:
// case 'FILTER_PROPERTIES'...
visibleProperties = action.properties.filter(item => item.bedroom >= action.filters.bedroom)
Is this approach totally fine?
From the documentation of dispatch:
Dispatches an action. This is the only way to trigger a state change.
The store's reducing function will be called with the current
getState() result and the given action synchronously. Its return value
will be considered the next state. It will be returned from getState()
from now on, and the change listeners will immediately be notified.
It's a synchronous function and it's totally fine to use in the way you've described(as long as setFilter is synchronous). However, if you're doing asynchronous operation in setFilter,(assuming that Promise returned from setFilter) you should chain your dispatch calls like this:
dispatch(setFilter('bedroom', parseInt(value))).then(() => {
// New state
const { filters, properties } = getState()
// 'FILTER_PROPERTIES' action (Depending on new state)
dispatch(filterProperties(properties, filters))
}
Another option might be using selectors. Please check it out:
https://github.com/reactjs/reselect
I'm trying to do this relatively complex operation in BaconJs.
Basically, the idea is keep trying each check until you have a 'pass' status or they all fail. The catch is that 'pending' statuses have a list of Observables (built from jquery ajax requests) that will resolve the check. For performance reasons, you need to try each Observable in order until either they all pass or one fails.
Here's the full pseudo algorithm:
Go thru each check. A check contains an id and status = fail/pass/pending. If pending, it contains a list of observables.
If status = pass, then return the id (you're done!)
if status = fail, then try the next check
if status = pending
try each observable in order
if observable result is 'false', then try the next check
if reach end of observable list and result is 'true', then return the id (you're done!)
Here's the Bacon code. It doesn't work when the Observables are Ajax requests.
Basically, what happens is that it skips over pending checks....it doesn't wait for the ajax calls to return. If I put a log() right before the filter(), it doesn't log pending requests:
Bacon.fromArray(checks)
.flatMap(function(check) {
return check.status === 'pass' ? check.id :
check.status === 'fail' ? null :
Bacon.fromArray(check.observables)
.flatMap(function(obs) { return obs; })
.takeWhile(function(obsResult) { return obsResult; })
.last()
.map(function(obsResult) { return obsResult ? check.id : null; });
})
.filter(function(contextId) { return contextId !== null; })
.first();
UPDATE: the code works when the checks look like this: [fail, fail, pending]. But it doesn't work when the checks look like this: [fail, pending, pass]
I am more familiar with RxJS than Bacon, but I would say the reason you aren't seeing the desired behavior is because flatMap waits for no man.
It passes [fail, pending, pass] in quick succession, fail returns null and is filtered out. pending kicks off an observable, and then receives pass which immediately returns check.id (Bacon may be different, but in RxJS flatMap won't accept a single value return). The check.id goes through filter and hits first at which point it completes and it just cancels the subscription to the ajax request.
A quick fix would probably be to use concatMap rather than flatMap.
In RxJS though I would refactor this to be (Disclaimer untested):
Rx.Observable.fromArray(checks)
//Process each check in order
.concatMap(function(check) {
var sources = {
//If we pass then we are done
'pass' : Rx.Observable.just({id : check.id, done : true}),
//If we fail keep trying
'fail' : Rx.Observable.just({done : false}),
'pending' : Rx.Observable.defer(function(){ return check.observables;})
.concatAll()
.every()
.map(function(x) {
return x ? {done : true, id : check.id} :
{done : false};
})
};
return Rx.Observable.case(function() { return check.status; }, sources);
})
//Take the first value that is done
.first(function(x) { return x.done; })
.pluck('id');
What the above does is:
Concatenate all of the checks
Use the case operator to propagate instead of nested ternaries.
Fail or pass fast
If pending create a flattened observable out of check.observables, if they are all true then we are done, otherwise continue to the next one
Use the predicate value of first to get the first value returned that is done
[Optionally] strip out the value that we care about.
I agree with #paulpdaniels Rx-based answer. The problem seems to be that when using flatMap, Bacon.js won't wait for your first "check-stream" to complete before launching a new one. Just replace flatMap with flatMapConcat.
Thanks to #raimohanska and #paulpdaniels. The answer is to use #flatMapConcat. This turns what is basically a list of async calls done in parallel into a sequence of calls done in order (and note that the last "check" is programmed to always pass so that this always outputs something):
Bacon.fromArray(checks)
.flatMapConcat(function(check) {
var result = check();
switch(result.status) {
case 'pass' :
case 'fail' :
return result;
case 'pending' :
return Bacon.fromArray(result.observables)
.flatMapConcat(function(obs) { return obs; })
.takeWhile(function(obsResult) { return obsResult.result; })
.last()
.map(function (obsResult) { return obsResult ? {id: result.id, status: 'pass'} : {status: 'fail'}; });
}
})
.filter(function(result) { return result.status === 'pass'; })
.first()
.map('.id');