jeffbski / redux-logic Goto Github PK
View Code? Open in Web Editor NEWRedux middleware for organizing all your business logic. Intercept actions and perform async processing.
License: MIT License
Redux middleware for organizing all your business logic. Intercept actions and perform async processing.
License: MIT License
I'd like to set the warnTimeout default much lower for all my logic, is that possible?
Not sure if this is the correct place for this.
I am currently using redux logic and i'm trying to deal with a paginated API.
I am trying to pass dispatch to an auxiliary function that will attempt to pull all the pages of the resultset and concat them to the store.
I pass the dispatch to this auxiliary function and it works fine, but when i recurse on this same auxiliary function, to try to pull the next set of results and pass the dispatch in again, it doesn't actually dispatch any actions, and it doesn't throw any errors.
I have made sure multiple dispatches were turned on by dispatching the same action 3 times before the recursion started and that worked.
Any ideas why the dispatch wont work past the first auxiliary function call?
Please include all import statement in examples. Dont assume we know where things are being imported from.
Also, just another point.. this is awesome.
Also, any chance you can make this lighter? RXjs is cool, but such a large dep. I believe this could be done far lighter. Just an intuition..
createLogic({type: actions.UPLOAD, latest:true, process({action}, dispatch, done){ console.log("dispatch: " +JSON.stringify(dispatch)) });
returns undefined for dispatch.
Please help.
Thanks
const winner = yield race({
auth: call(getAuthorize, { username, password, isRegistering: false }),
logout: take(LOGOUT),
});
// If `authorize` was the winner...
if (winner.auth) {
// ...we send Redux appropiate actions
yield put(changeForm({ username: '', password: '' })); // Clear form
forwardTo(pages.pageDashboard.path); // Go to dashboard page
// If `logout` won...
} else if (winner.logout) {
// ...we send Redux appropiate action
yield call(logout); // Call `logout` effect
forwardTo(pages.pageLogin.path); // Go to root page
}
Is it possible to produce a race like this saga ?
In this competitive world of JS libraries, many people won't even look at something unless there are compelling testimonials by other users, so I'd like to add some of your comments to the redux-logic home page to encourage others to give it a try.
If you would be willing to leave a short 1-2 sentence testimonial comment along with your name and any title/company that you want included then I'll add those to the README. You can add those as a comments on this issue or if you would rather email me, that's fine too. My email is available off the github profile here
Thanks in advance!
Hi,
Currently I'm struggling with the use of Flow in combination with redux-logic because Flow does not understand that a given logic will only receive actions of a given type.
Given the following types:
export type Action =
{ type: 'SEARCH_LOCATION_SELECT', payload: string }
| { type: 'GEOLOCATION_REQUEST' };
export type Dispatch = (action: Action) => any;
export type LogicContextT = {
action: Action,
getState: () => GlobalState
}
This is a problem for Flow:
const aroundMeSelectLogic = createLogic({
type: 'SEARCH_LOCATION_SELECT',
process({ action }: LogicContextT, dispatch: Dispatch): void {
if (action.payload === 'AROUND_ME') {
dispatch(geolocationRequest());
}
}
});
property
payload
. Property not found in object type
and this is NOT a problem for Flow:
const aroundMeSelectLogic = createLogic({
type: '*',
process({ action }: LogicContextT, dispatch: Dispatch): void {
if (action.type === 'SEARCH_LOCATION_SELECT' && action.payload === 'AROUND_ME') {
dispatch(geolocationRequest());
}
}
});
Does anyone have a suggestion on how to best handle this?
It would be great to have retryWhen policy as a configuration to createLogic. So, it will be easier to implement retry policy without digging into observables.
Ideally, just expose predicate function with some context information as a parameter to that function.
retryWhen(({retry, action, cancelled$}) => {
// retry object may contain logic or retry specific options
// and some methods to deal with retry policy
retry.delay(retry.throttle);
return retry.errorCount <=2;
})
Hi there, I am seeing the attached error occurring in my React-Native app:
The error occurs in the following sequence
The referenced logic is
export const fetchMarkersLogic = createLogic({
type: MARKERS_FETCH_REQUEST,
latest: false, // take latest only
process({ firebase, action }, dispatch) {
const { currentUser } = firebase.auth();
firebase.database().ref(`/markers/${currentUser.uid}`)
.on('value', snapshot => {
const markers = _toArray(snapshot.val());
dispatch(
{ type: MARKERS_FETCH_SUCCESS, payload: markers },
{ allowMore: true } // Allow multiple dispatch
);
})
;
}
});
When logging out the user, I've tried executing dispatch() and a dispatch with allowMore: false, but the error still occurs.
Any ideas?
Is there and example showing how to use redux-logic inside the react-boilerplate?
The issue is that you cannot have a single rootLogic that imports all the application logic because this will prevent code splitting by route that is built into this boilerplate.
I need some help on how to replace the redux-saga with redux-logic and still maintain the code splitting intact.
Thank you
I'm writing an app in TypeScript, and it would be nice to have definitions to aid in syntax completion, etc.
Provided that you're open to this, I'll finish what I've written so far and submit a pull request.
Let's say a have a logic for a generic data fetching which allows for fetching from multiple endpoints. Fetching happens after a particular action is dispatched (like FETCH_DATA
), but action has additional metadata, for example:
type: FETCH_DATA,
meta: {
endpoint: '...',
resourceID: '...',
}
I want to implement remote autocomplete (fetch more specified data as user types), so I thought of using latest
, debounce
etc. options with the logic. The problem is when I want to dispatch multiple FETCH_DATA
for multiple resources (say users
and orders
) - logic only looks at the action type when debouncing etc. I think it may be useful to allow for (optional) custom function that would check for action equality. What do you think, @jeffbski?
Hi guys, I have been looking at this issue for an hour now and I can't seem to find the issue here.
So have piece of logic that first validates the payload before executing the process function. But when the process method executes it fails to dispatch another action. When I comment out the the validation method the process function seems to be running fine. I have tested if the process does get called when the validation method runs and it seems it does it executes the post method and resolves the promise but it fails to dispatch.
Here is the logic that I wrote
type:${NAME}/${REGISTER},
latest: true,
validate({getState, action}, allow, reject){
let {email, password, confirmPassword} = action.payload
let {isEmpty, isEmail} = validator
let state = getState()
if(isEmpty(email)){
state[NAME] = {
...state[NAME],
forms:{
...state[NAME].forms,
isLoading : false,
isSuccess: false,
isError: true,
responseMessage: 'Email is required'
}
}
reject(action)
}
else if( !isEmpty(email) && !isEmail(email))
{
state[NAME] = {
...state[NAME],
forms:{
...state[NAME].forms,
isLoading : false,
isSuccess: false,
isError: true,
responseMessage: email + ' is not a valid email!'
}
}
reject(action)
}
else if( isEmpty(password))
{
state[NAME] = {
...state[NAME],
forms:{
...state[NAME].forms,
isLoading: false,
isError : true,
isSuccess: false,
responseMessage: 'Password is required'
}
}
reject(action)
}
else if( !isEmpty(password) && isEmpty(confirmPassword)){
state[NAME] ={
...state[NAME],
forms:{
...state[NAME].forms,
isLoading:false,
isError: true,
isSuccess: false,
responseMessage:'Please confirm your password'
}
}
reject(action)
}
else if( !isEmpty(password) && !isEmpty(confirmPassword))
{ if(!(password === confirmPassword))
{
state[NAME] ={
...state[NAME],
forms:{
...state[NAME].forms,
isLoading:false,
isError: true,
isSuccess: false,
responseMessage:'Password Confirmation does not match'
}
}
}
reject(action)
}
allow(action)
},
processOptions:{
dispatchReturn: true,
successType: ${NAME}/${REGISTER_SUCCESS},
failType: ${NAME}/${REGISTER_FAILURE}
},
process({getState, action,http}, dispatch){
const apiURL = ctx.flags.__DEV__
? 'http://${ctx.options.app.domain}:9002'
: ''
let dataObj = JSON.stringify({
email: action.payload.email,
password: action.payload.password
})
return http.post(apiURL+ '/users', dataObj, {
headers: {
"Content-Type":"application/json"
}
})
.then(res=>{
return res
})
.catch(
err =>{
console.log('PROMISE REJECTED')
return err
})
},
})```
One of the potentially confusing parts of the current API is for multi-dispatching.
For the logic to know when it is complete we need to somehow communicate that in our code.
If you dispatch an observable or use processOptions.dispatchReturn (where you can return an object, error, promise, or observable) then we can know from these when the code is done, so that works great for those use cases.
However I don't want to force everyone to use promises or observables so we allow the developer to signal completion by expecting a single call of the dispatch function. Since I think the most common use case is to do a single dispatch, that makes a good fallback plan. However when the user wants to make multiple dispatches then it gets tricky. They need to either dispatch an observable or use the second parameter in the dispatch function dispatch(action, { allowMore: true })
to indicate they aren't done and then the final dispatch without allowMore
set to end.
So I think this could be confusing to people once they want to do multiple dispatches and they are not using observables. They would likely not remember they have to do something special to enable that.
After reviewing how redial
works, it allows free dispatching but uses a returned promise to indicate completion. Thus it doesn't do anything with the value coming back, but uses that to signal it is done. It is expected that the user will dispatch any values or errors but returning the promise to signal it is done.
This got me thinking that we could do the same thing.
Here's how it could work:
First if processOptions.dispatchReturn is true, then nothing would change, it would still dispatch the object, error, or results from promise/observable returned.
So assuming you need to do multiple dispatches and are not using an observable to do that, then a different process option could enable that.
processOptions: {
// enables free dispatching if the code returns a promise or observable
completeReturn: true // complete when the returned promise/observable is done
}
Thus if the code returns a promise or observable that would be used to signal completion. This would enable the dispatch to be free to be used for multi-dispatching. If the user doesn't return a promise or observable, then we'd expect a single dispatch as the way it works today (or they can still override with the allowMore flag).
By allowing free dispatching in the promise code, it makes it easy to dispatch things in different steps of the promise chain as you receive the results.
It is also nice that async functions (async/await) return a promise. So I can imagine many people using that when they need to perform many operations in one set of code. Simply by returning the result of the async function (a promise) they will be able to use free (multiple) dispatching as well.
So I'm currently thinking that this might even be a good default. If they enable dispatchReturn
that takes priority and goes the extra step of dispatching for them, but otherwise by default completeReturn could be defaulted to on.
This feature only becomes active if they return a promise or observable, so if they don't then it works as it does today (expecting single dispatch). But once they start returning a promise or observable (or returning from an async function) then we start using that to determine completion.
What do you think? Any thoughts or other ideas?
When turn on dispatchReturn in processOptions and after reject action in a validate hook, action with successType will be dispatched demo on jsfiddle, pay attention to lines 30 and 96.
I'm thinking it might be nice to create a separate helper project which will help make it easy to create mock stores for integration level tests. It would basically let you setup your test store in one line of code hooking up as much or as little as you want to specify.
Then it provides a few nice methods to facilitate testing like knowing when the logic is complete and then being able to see an array list of actions that were dispatched.
Let me know what you think. I want it to be simple but yet including the right things to make testing easy.
import { createMockStore } from 'redux-logic-test';
// specify as much as necessary for your particular test
const store = createMockStore({
initialState: optionalObject,
reducer: optionalFn, // default: identity reducer fn(state, action) { return state; }
logic: optionalLogic, // default: [] - used for the logicMiddleware that is created
injectedDeps: optionalObject, // default {} - used for the logicMiddleware that is created
middleware: optionalArr // other mw, exclude logicMiddleware from this array
});
store.dispatch(...) // use as necessary for your test
store.whenComplete(fn) - shorthand for store.logicMiddleware.whenComplete(fn) - when all inflight logic has all completed calls fn and returns promise
store.actions - the actions dispatched, use store.resetActions() to clear
store.resetActions() - clear store.actions
store.logicMiddlware // access logicMiddleware automatically created from logic/injectedDeps props
// allows you to use store.logicMiddleware.addLogic, mergeNewLogic, replaceLogic, whenComplete, monitor$
// So a test might look something like
const store = createMockStore({
initialState: { foo: 42 },
logic: [ fooLogic, barLogic ], // logicMiddleware will be created for this logic and injectedDeps
injectedDeps: { api: myAPI }
});
store.dispatch({ type: FOO });
store.dispatch({ type: BAR });
store.whenComplete(() => { // when logic is finished
expect(store.actions).toEqual([ // check the actions that were dispatched
{ type: FOO },
{ type: BAR },
{ type: FOO_SUCCESS, payload: [] },
{ type: BAR_SUCCESS, payload: {} }
]);
});
I didn't require a reducer so I didn't need to create one, but if you test needs that you can supply one.
I have a gist of the API here
Hi ,
I am checking the simple example http://jsfiddle.net/jeffbski/954g5n7h , and its using latest: true
, that means it has to cancel previous requests as per documentation.
But I observed its not cancelling the previous requests, instead its waiting until all the previous requests are completed, is it correct way , am I understanding the feature wrongly ?
It's probably anti design pattern, but because of the all the utilities in it, I found it pretty convenient to store my async logic using redux-logic.
I have a case where I need to display an html table with async data.
The example case is the HAL table that needs to fetch it's data to populate it's content.
At the end the component is updated with :
this.setState({
page: this.page,
employees: employees,
attributes: Object.keys(this.schema.properties),
pageSize: pageSize,
links: this.links
});
I wonder if I can move this in a logic so I can use async
, await
and all my utilities.
Can I use redux-logic for updating the component state ?
I wanted to see if anyone has any thoughts about this. I'm thinking it would be best to deprecate the process hook's single-dispatch mode which comes into play when you use the signature process({ getState, action }, dispatch)
// single dispatch mode enabled by including dispatch but not done
process({ getState, action }, dispatch) {
dispatch(newAction) // call dispatch exactly once
}
We would keep the other two process hook signatures:
process({ getState, action }, dispatch, done)
// multi-dispatchprocess({ getState, action })
// return dispatchingWe would also deprecate the dispatchMultiple option and add a warnTimeout option.
Here's some background on the issue and my proposed plan for deprecating is at the end.
The process hook has a variable signature that enables three modes currently (multi-dispatch, single-dispatch, and return dispatching). The original mode single-dispatch is confusing and easily misused by users.
Deprecating and eventually eliminating single-dispatch mode should reduce confusion and prevent many mistakes.
A new warnTimeout (configurable by option) will inform users in development if they are failing to call done after finishing dispatching. It can be disabled for intentional never ending logic.
In that single-dispatch mode, the process hook expects dispatch to be called exactly once.
This is problematic since newcomers don't realize the nature of this, so they copy this single-dispatch code and will try to use it for multiple dispatches accidentally. It isn't obvious from the signature that they can't do this.
Another problem with single-dispatch mode is that since it requires dispatch to be called exactly once, you have to call it empty if you don't want to dispatch anything dispatch()
which is also counter intuitive.
So while it seemed like a good idea originally for a common use case, it ends up being more confusing for people and that's not good.
However switching to the full signature (multiple-dispatch) form of process solves all of these problems with the slight disadvantage of an extra line of code to call done.
// normal multiple-dispatch mode
process({ getState, action }, dispatch, done) {
dispatch(newAction) // call dispatch zero to many times
done(); // call done when finished dispatching
}
To avoid confusion, I've changed the examples to use this since it is safer for people to learn from and they can do 0 to many dispatches as they see fit.
So going forward it is my proposal that we support only two of the three signatures for the process hook, dropping the single-dispatch mode.
process({ getState, action }, dispatch, done)
// normal multi-dispachprocess({ getState, action })
// return dispatchingFor those that haven't seen the second form which was introduced with processOptions is great for working with promises, async/await, and observables. It uses the return value for dispatching:
You can also influence the dispatching with processOptions as mentioned in the docs.
logic is still running after 60s, forget to call done()? For non-ending logic, set warnTimeout: 0
.single-dispatch mode is deprecated, call done when finished dispatching. For non-ending logic, set warnTimeout: 0
dispatchMultiple is always true in next version. For non-ending logic, set warnTimeout to 0
(We would delay v0.13 for at least a couple weeks to allow for consumers to get deprecation warnings in development)
warnTimeout: 0
insteaddone must be called when dispatching is finished or never ending logic should set warnTimeout: 0.
dispatchMultiple option is no longer needed, set warnTimeout:0 for non-ending logic
Hi @jeffbski,
I have a use case where given an action I might modify it, reject it, or do nothing to it. However, when I reject it I want to dispatch a new action in order to have some information persisted to the state.
I notice that transform only has the next/reject methods available to it, is there a technical reason why dispatch is not there?
Thanks for your time,
Ivo
For example, for a logic I created, can I reuse it without redux?
Maybe this is not a good question. Because I can always extract very common part into a separate library for reusing.
I am just wondering what do you think.
By the way, I like this library a lot. It has the potential to be come a popular one.
Firstly, just wanted to say that I'm loving the library.
Being able to pass a dep
into createLogicMiddleware
is really useful , especially for testing scenarios, however there are scenarios where the dependency needs access to the store.
For instance we have a api
dependency, however the access token it needs is contained in the store. Right now, within the logic we have to set the access token on api
by using getState
, it would be nice if we could register api
in deps
as a function that takes state
and returns the api
.
I am using redux-connect to load initial data.
When using thunks, I can simply do:
const resolve = [{
promise: ({params, store}) => store.dispatch(init(params.layout))
}];
store.dispatch
returns a promise.
What's the proper way to implement it in redux-logic?
Below is my solution, but I think there should be more elegant way to do it.
const resolve = [{
promise: ({params, store}) => {
const defer = {};
defer.promise = new Promise((res, rej) => {
defer.resolve = res;
defer.reject = rej;
});
store.dispatch(init(params.layout, defer));
return defer.promise;
},
}];
The logic handler calls defer.resolve()
before calling done()
.
init()
is a simple action creator.
I am trying to throw an error in my promise chain with the following code :
processOptions: { // options configuring the process hook below
// on error/reject, automatically dispatch(repoLoadingError(err))
failType: requestError, // action creator which accepts error
},
// perform async processing, Using redux-logic promise features
// so it will dispatch the resolved action from the returned promise.
// on error, it dispatches the using the action creator set in
// processOptions.failType above `repoLoadingError(err)`
// requestUtil was injected in store.js createLogicMiddleware
process({ getState, authService, action }, dispatch) {
const { username, password } = action.data;
dispatch(sendingRequest(true));
let codeUrl;
return authService.preLogin()
.then((res) => authService.login(username, password))
.then((jwt) => {
if (jwt) { // force the error
return Promise.reject(new Error('It appear there is a problem with your account, please contact an administrator.'));
}
dispatch(put(jwtLoaded(jwt)));
});
The reject doesn't get caught :
logic.js?d876:74 Uncaught (in promise) Error: It appear there is a problem with your account, please contact an administrator
This is the content of a authService.login
:
login(username, password) {
const options = {
method: 'POST',
headers: {
'Accept': 'application/json', // eslint-disable-line quote-props
'Content-Type': 'application/x-www-form-urlencoded',
},
};
const queryString = toQueryString({ username, password });
return request(`${url.login}?${queryString}`, options)
.then((response) => Promise.resolve(response));
},
I have the feeling that it is not possibme to dispatch within the promise.
Is it a bug or is it me ?
I've noticed a weird bug in an application using redux-logic. I'm using axios to post to a third party API. Using developer tools, the request shows up twice under the network tab. Options and the final post.
I suspect the success process options is triggering twice as a result of the options request.
I'm having a difficult time replicating the issue using the reqres.in service thou.
Is this a possibility or could it be related to another bug deeper in my code base.
Many thanks.
Looks like the RX umd package referenced by the RX jsfiddle samples no longer exists thus breaking the samples
https://npmcdn.com/@reactivex/[email protected]/dist/global/Rx.umd.js
Not found: file "/dist/global/Rx.umd.js" in package @reactivex/[email protected]
Appears that Rx.umd.js
has been renamed to Rx.js
so the new url is:
https://npmcdn.com/@reactivex/[email protected]/dist/global/Rx.js
I'm having trouble with errors being swallowed and never bubbling up. I can't seem to use the promise .catch()
, or wrap in a try/catch.
Specifically, when I dispatch an action that results in a state change, React re-renders my components. If React throws an error during the render method I can't seem to get ahold of it.
I don't know much about RxJS/Observables... but I can see that my error is being caught by RxJS here https://github.com/ReactiveX/rxjs/blob/master/src/Subscriber.ts#L260-L265 and then I don't know where it goes. I'm assuming redux-logic should throw the error being passed back by RxJS, or use the failType
or UNHANDLED_LOGIC_ERROR
action types to someone notify me of this error.
As it stands now... it's just silent and everything stops. Any help, or an explanation would be helpful. What is the proper way to handle errors like this with redux-logic?
Example code:
export const someLogic = createLogic({
type: ACTION_TYPES.LOGIC_EXAMPLE,
process({ getState, action }, dispatch, done) {
axios.get('/data')
.then(res => {
dispatch(setUser({ user: {name: 'crash render()'} }))
}).catch(err => {
// never gets called
console.error(err)
}).then(() => done())
}
})
Thanks!
I don't know if this is a problem with redux-logic
, per se. But I do see these kinds of errors so much that I'm curious is there's something that I should be doing better in order to catch them so they createLogicAction$
isn't the end of the line for the error.
It's important to note that the error here is happening in my VirtualizedGrid
's render function, and as best as I can tell, there are no errors within any dispatched actions.
I'd like to be able to to bubble up errors like this to the user through a toast. I have code in place for things like that, but it seems that since the "final stop" for these is in redux-logic
, my other code doesn't have a chance to grab it,
Have you or anyone else see issues like this?
Hi@all,
is there an example of how to implement the login flow using redux-logic and JWT? I'm very much interested in how to implement the refresh token logic to be specific. Could this be something that should be done in the validate function?
Thanks for any help
https://jsfiddle.net/jeffbski/954g5n7h/
Why is it that in the above fiddle, if you click the 'fetch users' button 5 times it takes 10s to respond? Shouldn't the next event either cancel the previous one, or at least be done in parallel?
Even if you dispatch a 'USERS_FETCH_CANCEL' (by clicking 'cancel') between each 'USERS_FETCH' you still get this behaviour. The request is still in pending state, even after clicking cancel, and additional requests are waiting for previous ones.
Thanks!
I'm getting cannot assign logicMiddleware instance to multiple stores, create separate instance for each
when try to load it on server few times.
I need to dispatch a logout event when I receive a err.statusCode = 401
.
So far, I have two solutions available:
You recommended us in the documentation to use Observable
to cancel an ajax call.
Since fetch
doesn't have a way to cancel an ajax request, and also can't be configured globally, I think it will be a nice feature if you add the possibility to catch globally the logic errors.
I have a use case where actions are to be processed in order in the following way:
Event order:
---a1---a2---a3--b1---b2--b3--a4---b4
Processing Order
---a1---a2---a3----------------a4------
-----------------b1---b2--b3---------b4
All actions need to be processed in order and a2 should be dispatched to Redux logic only after a1 has been processed by reducer and the state of 'a' is updated in store. a1 can also have some side effects that could in turn generate a11, a12 actions. which again have to be processed in order. After all the events with respect to a1 have been processed by reducers and state updated in store, I want a2 to be dispatched to redux-logic. Similarly I want to process 'b' events without blocking for a.
Is the above possible?
I am using redux-logic-test
for testing.
I have a logic that initializes the socket and dispatches actions.
warnTimeout
is set to 0
.
In my unit tests, I call await store.whenComplete()
, but it causes timeouts because the logic never calls the done()
callback.
If I call done()
, all actions are ignored when I call dispatch({...})
.
Is there any workaround for this?
Jeff,
I am having a problem with a basic fetch using processOptions. I tried to follow the example but it isn't working for me. Yet the rxjs style does. Any help would be great. I have included the gist below:
https://gist.github.com/StevenTCramer/7a09f1739f4dc17cc25e546bbbd62f89
I tried looking through the source to see if I could figure out my error, but I'm too much a newb to accomplish it tonight.
Thanks.
The section on cancellation states
Many libraries like axios and fetch don't support aborting/cancelling of an in-flight request
However this is no-longer the case with axios which now supports cancellation tokens to cancel requests (see https://github.com/mzabriskie/axios#cancellation)
I am currently using the cancelType
in my logic to prevent the results the being dispatched, but as you state in the documentation, this does not actually cancel the inflight request.
Is there a hook or anyway to pass a cancellation token to redux-logic so that when the cancelType
is dispatched, cancel()
is called on the cancellation token.
Our usecase is that we add toasts with payload { "type": "TOASTS_ADD", "payload" { "id": <toast_unique_id>, "timeout": <optional_timeout>, "message": <message> } }
and remove them with payload { "type": "TOASTS_REMOVE", "payload": <toast_unique_id>}
. Removal is done manually or on timeout.
If it is done manually then we would like to cancel logic (waiting on toast timeout) by <toast_unique_id>
. From API it seems that only type can be used for cancellation, without any payload support.
Therefore we've created "workaround":
const clearTimeoutMap = new Map();
const logic1 = createLogic({
type: 'TOASTS_ADD',
process({ action: { payload: { timeout, id } } }, dispatch, done) {
if (typeof timeout === 'number') {
const to = setTimeout(() => {
clearTimeoutMap.delete(id);
dispatch(toastsRemove(id));
done();
}, timeout);
clearTimeoutMap.set(id, () => {
clearTimeoutMap.delete(id);
clearTimeout(to);
done();
});
} else {
done();
}
},
});
const logic2 = createLogic({
type: 'TOASTS_REMOVE',
process({ action: { payload: id } }) {
const ct = clearTimeoutMap.get(id);
if (ct) {
ct();
}
},
});
Is this approach OK?
I love redux logic. Dude, this is great. I have a problem though... I'm addicted to making things very tiny. I know. It doesn't matter. I know size in the JS world is not really a problem. Most apps are just images anyway. But still. Before I go and do something stupid:
(1) if I built this LIB without RxJs/Observables what problems would I run into
(2) could I build a tiny faximily library and still handle things like (debouncing and throttling) without RxJS
(3) great work, I love this library.
Check the code here: https://github.com/tylerlong/hello-async/blob/1d6d7159a4528b1af7a560ad9c8232b7e6c9493e/redux-logic/src/logics.js
I can show the notification, but cannot hide it.
I checked the examples in your repo and I find that dispatch
is only invoked once in each logic. And I guess that's the limitation of this middleware.
Perhaps community would benefit from a small example using web sockets in addition to classic ajax requests. I'm clear on how to send messages to the server but not sure how to deal with messages arriving from the server.
Is it possible to call the done
callback automatically, if the process
function is executed or the returned promise is resolved?
I am revamping my current app to use redux-logic, and this is a very common use case for me.
export const someLogic = createLogic({
type: SOME_ACTION,
async process({apiClient}, dispatch, done) {
const data = await apiClient.getSomeData();
if (data.something) {
done();
return;
}
// do more stuff here
done();
},
});
It could be converted to this:
export const someLogic = createLogic({
type: SOME_ACTION,
autoDone: true,
async process({apiClient}, dispatch, done) {
const data = await apiClient.getSomeData();
if (data.something) {
return;
}
// do more stuff here
},
});
Such syntax would be much useful for me because there is always a risk to forget to call done()
.
Following #21,
The example you provided works for the promise chaining processing style.
This is not the writing you recommanded to me, I have this two process that require unit test:
Global logic of logout, will be present on every pages:
export const getLogoutLogic = createLogic({
type: SUBMIT_LOGOUT_REQUEST, // trigger on this action
latest: true, // use response for the latest request when multiple
async process({ pages, authService, forwardTo }, dispatch, done) {
try {
const success = await authService.logout();
dispatch(sendingLogoutRequest(false));
forwardTo(pages.pageLogin.path);
dispatch(onSuccessLogoutRequest(success));
} catch (err) {
dispatch(sendingLogoutRequest(false));
forwardTo(pages.pageLogin.path);
dispatch(onErrorLoginRequest(err));
}
done();
},
});
Login logic, will be present on LoginPage:
export const getAuthorizeLogic = createLogic({
type: SUBMIT_LOGIN_REQUEST, // trigger on this action
cancelType: LOCATION_CHANGE, // cancel if route changes
latest: true, // use response for the latest request when multiple
async process({ authService, forwardTo, pages, action }, dispatch, done) {
const { username, password } = action.data;
try {
const jwt = await performLogin(authService, username, password);
dispatch(onSuccessLoginRequest());
dispatch(jwtLoaded(jwt));
forwardTo(pages.pageDashboard.path); // Go to dashboard page
} catch (err) {
dispatch(onErrorLoginRequest(err));
forwardTo(pages.pageLogin.path); // Go to dashboard page
}
done();
},
});
async function performLogin(authService, username, password) {
await authService.preLogin();
await authService.login(username, password);
const codeRes = await authService.code(oauthClient.clientId, oauthClient.redirectUri);
const code = getParameter('code', codeRes.url);
const jwt = await authService.token(oauthClient.clientId, oauthClient.clientSecret, code,
return jwt;
}
In your example:
const resultPromise = getAuthorizeLogic.process({
pages,
authService,
action,
forwardTo,
getState,
requestUtil
}, dispatch, done);
Even with a mock for my test, this doesn't return a promise, but instead undefined
because I don't do any return in my process.
I am also surprise there is no otherway to test the code "line by line", for example, for await return value checking.
I also need a way to test when errors happen, so my app doesn't get unsynced with it's store.
Thanks in advance.
When using thunks, UI side-effects such as route rediection, toast notifications etc, can be handled by chaining onto the promise returned by the thunk
For example in a form submit handler
handleSubmit(values) {
return createEvent(values)
.then(() => routerActions.push('/'))
}
To accomplish the same using logic I'm having to intercept the creation success action and do the redirection there
For example
const createUnpublishedActivityFulfilledLogic = createLogic({
type: createUnpublishedActivityFulfilled,
process({ routerActions }) {
return routerActions.push('/')
},
})
It doesn't seem right to me to have UI concerns within logic. It now means that the create action is no-longer re-usable as it will always cause a redirection on success.
Is there another way to handle this scenario?
Enhance the validate block with a waitFor option.
An example:
During init, an application validates a user's session and populates auth state. Should a redux-logic function depend on auth state but execute prior to auth processes being processed, the validate block can delay execution till a known action is received.
Hi there,
I'm building a test app with RN that uses the redux-logic library. I have several pieces working, but I recently ran into an issue that I can't resolve.
I'm using the firebase database 'on' event, that will automatically trigger whenever the backend database is changed. For example, I have a db of users, and if I add a new user the event is triggered, which does a dispatch to update my redux store.
Here is the docs on the FB call:
https://firebase.google.com/docs/database/web/read-and-write
Here is the code snippet for my logic:
export const employeesFetchLogic = createLogic({
type: EMPLOYEES_FETCH_REQUEST,
latest: false, // take latest only
// firebase injected
// from configureStore logic deps
process({ firebase }, dispatch) {
const { currentUser } = firebase.auth();
// Get all the employees from firebase
firebase.database().ref(`/users/${currentUser.uid}/employees`)
.on('value', snapshot => {
// THIS DISPATCH ONLY WORKS THE 1ST TIME!
dispatch({ type: EMPLOYEES_FETCH_SUCCESS, payload: snapshot.val() });
})
;
}
});
It works fine the 1st time its called after I add a new record when the app 1st loads, but the dispatch is not passed through to my reducer the second or subsequent times. I stepped through the debugger and can see the event being triggered every time and the reducer being called the 1st time, but the dispatch is not passed through to the reducer after the 1st time.
Any ideas why this could be?
regards,
Royce
Hi. I'm using the auto-dispatch method now in most all of my "logics". However, I have found that unless a Promise is explicitly rejected, the error message doesn't seem to be added to the resulting failType
's payload, and no message is written to the console.
Would it be possible to check and add error.message
to the payload if it exists?
Here is a sample that you can setup to try this out.
const testingLogic = createLogic({
type: 'TESTING',
latest: true,
processOptions: {
successType: 'TESTING_SUCCESS',
failType: 'TESTING_FAILURE'
},
process({action}) {
return new Promise((resolve, reject) => {
if (action.payload === 'fail') {
reject('Failed with reject');
} else if (action.payload === 'pass') {
resolve('passed');
} else {
const foo = action.get('foo'); // There is no `get` method on action
}
});
}
});
Then, in Redux tools, dispatch the following:
{
type: 'TESTING',
payload: 'fail'
}
This will result in a nice action with an error message:
{
type: 'TESTING_FAILURE',
payload: 'Failed with reject',
error: true
}
However, sending foo
through as the payload
like so:
{
type: 'TESTING',
payload: 'foo'
}
Results in a dispatched action with no error message, and of course, no console message either.
{
type: 'TESTING_FAILURE',
payload: {},
error: true
}
Hello. Is there a way to configure Logic to fire when some deep state changes?
It would be a different approach where instead of running business code on FETCH_POLLS
there would be only dispatched action like FETCH_POLLS_START
which would set state polls.fetching
to true
. A Logic would run when it would detect transition of polls.fetching
from false
to true
. This would bring interception based not on action type but on state change, which is even more general.
There are projects like redux-watch which solves only this part. Could it be conbined with redux-logic?
What do you think?
Hi there, I've just started using this library and its going well except for an issue that I haven't been able to resolve. I'm not sure what's causing it, but it started when I installed this library and the required RXJs libraray, and updated WebPack (1.13.2) and Babel (core 6.14) to handle the ES2016 features.
The babel-runtime is in dependencies, babel-plugin-transform-runtime is in devDependencies, I've tried clearing the NPM cache, removing and reinstalling node_modules, changing parameters on the webpack CommonsChunkPlugin, .bablerc, etc for 2 days and the exception is still thrown in the browser. Even odder, the application seems to be working, but I cant have this browser exception.
Here is the output from webpack --display-error-details
> webpack --display-error-details
clean-webpack-plugin: /Applications/MAMP/htdocs/tonight/build has been removed.
Hash: 5d841416e86a960afb3d
Version: webpack 1.13.2
Time: 58269ms
Asset Size Chunks Chunk Names
robots.txt 26 bytes [emitted]
vendor.b47001ac2219ce9e5fe0.js 3.09 MB 0, 2 [emitted] vendor
manifest.7f098e6c96f193b21664.js 763 bytes 2 [emitted] manifest
app.edc2af931ca5a3a1776b.css 16.2 kB 1, 2 [emitted] app
favicon.ico 1.15 kB [emitted]
index.html 2.04 kB [emitted]
app.edc2af931ca5a3a1776b.js 2.39 MB 1, 2 [emitted] app
styles/icon-style.css 1.14 kB [emitted]
fonts/icomoon.svg 8.34 kB [emitted]
fonts/icomoon.eot 3.26 kB [emitted]
fonts/icomoon.woff 3.18 kB [emitted]
fonts/icomoon.ttf 3.1 kB [emitted]
[0] multi vendor 496 bytes {0} [built] [1 error]
+ 2483 hidden modules
ERROR in multi vendor
Module not found: Error: Cannot resolve module 'babel-runtime' in /Applications/MAMP/htdocs/tonight
resolve module babel-runtime in /Applications/MAMP/htdocs/tonight
looking for modules in /Applications/MAMP/htdocs/tonight
/Applications/MAMP/htdocs/tonight/babel-runtime doesn't exist (module as directory)
resolve 'file' babel-runtime in /Applications/MAMP/htdocs/tonight
resolve file
/Applications/MAMP/htdocs/tonight/babel-runtime doesn't exist
/Applications/MAMP/htdocs/tonight/babel-runtime.js doesn't exist
/Applications/MAMP/htdocs/tonight/babel-runtime.jsx doesn't exist
looking for modules in /Applications/MAMP/htdocs/tonight/node_modules
resolve 'file' babel-runtime in /Applications/MAMP/htdocs/tonight/node_modules
resolve file
/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime is not a file
/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime.js doesn't exist
/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime.jsx doesn't exist
resolve 'file' or 'directory' /Applications/MAMP/htdocs/tonight/node_modules/babel-runtime
resolve file
/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime is not a file
/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime.js doesn't exist
/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime.jsx doesn't exist
resolve directory
directory default file index
resolve file index in /Applications/MAMP/htdocs/tonight/node_modules/babel-runtime
/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime/index doesn't exist
/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime/index.js doesn't exist
/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime/index.jsx doesn't exist
[/Applications/MAMP/htdocs/tonight/babel-runtime]
[/Applications/MAMP/htdocs/tonight/babel-runtime]
[/Applications/MAMP/htdocs/tonight/babel-runtime.js]
[/Applications/MAMP/htdocs/tonight/babel-runtime.jsx]
[/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime.js]
[/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime.js]
[/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime.jsx]
[/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime.jsx]
[/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime/index]
[/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime/index.js]
[/Applications/MAMP/htdocs/tonight/node_modules/babel-runtime/index.jsx]
Here is .babelrc
{
"presets": [
"es2015",
"es2016",
"react"
],
"plugins": [
["transform-runtime", {
"polyfill": false,
"regenerator": true
}],
"syntax-async-functions",
"transform-class-properties",
"transform-regenerator",
"transform-object-rest-spread"
]
}
Any help is appreciated
Do you find yourself creating constants in the following pattern?
SEARCH_FILTERS_LOAD_REQUEST
SEARCH_FILTERS_LOAD_SUCCESS
SEARCH_FILTERS_LOAD_FAILURE
The three constants above are associated with an asynchronous operation.
redux-logic
handles the first constant by making an asynchronous call (I use async/await). My reducer also handles the first constant by updating some "loading" state variable which controls various loading indicators in the UI.
Upon success or failure, redux-logic
dispatches one of the final two constants, which the reducer acts on (updating the search filters, setting the loading state to false, etc.), and perhaps dispatches an action to a toastr or modal store as well and then calls done()
.
Is this kind of three-pronged approach an anti-pattern?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.