Flutter redux epics - takeUntil action make stream unusable - redux

I am using flutter with redux and for handling stream data I'm using redux-epics.
Like this:
Stream<dynamic> getDataEpic(Stream<dynamic> actions, EpicStore<AppState> store) {
return Observable(actions)
.ofType(TypeToken<RequestDataAction>())
.flatMap((RequestDataAction action) {
return getDataStream()
.map(
(data) => UpdateStateWithData(data)
);
})
.takeUntil(actions.where((action) => action is CancelGetDataAction));
}
// getDataStream() - is a stream that returns some stuff...
In my screen in onInit I call store.dispatch(RequestDataAction()) and onDispose I call store.dispatch(CancelGetDataAction()) which destroys the Observable altogether, so the next time I go to this screen if I call store.dispatch(RequestDataAction()) my stream is not sending data, actually the entire Observable is dead!
How can I solve this problem? As far as I can see the problem is takeUntil because I completely closes the observable..

Try moving the takeUntil into the flatMap just after the getStreamData().map(). This will cancel the inner observable rather than the outer observable when the action is received.

Related

Pass Synchronous String Back In Async Thunk -- Avoid User Input Delay By Async Call

It's been a while since I've used redux so I may be taking the wrong approach here.
I am creating an autocomplete input.
If the input string is over 1 character, it will search a DB table column for possible options that the user can click to set the field.
I have the input field's value attribute tied to the redux store. My problem is there is a visible delay between pressing a key and seeing it show up in the input field, it makes a round trip to the database before your input is reflected in the input field (and store)...
<input
onChange={ (e) => dispatch(searchForProductInputOptions(e.target.value)) }
onFocus={ (e) => dispatch(setFocusForAutocomplete(e.target.name))}
className={css(styles.input)}
name={`${field.name}`}
autoComplete='off'
placeholder={`Search ${field.name}`}
value={ state.product[state.activePane][field.name].name}
/>
Here is the async thunk:
export const searchForProductInputOptions = createAsyncThunk(
'product/searchInput',
async (searchString, thunkAPI) => {
let model = thunkAPI.getState().productReducer.activeField;
if(searchString.length > 1) {
const response = await searchFieldInDatabase({model, searchString});
return response;
} else {
//short circuit this?
return searchString
}
// The value we return becomes the `fulfilled` action payload
}
);
You can see, for the first character I'm just sending the data back -- it's not async. I would like to send the search string back immediately, to update the state, but of course I need the result from the promise as well.
I looked into the .addCase(method.pending) but couldn't find a solution -- I felt like maybe I wasn't doing this right. Do you dispatch two methods from the onChange? What's the preferred method to send back synchronous data and then fire off the async request.

How to have a Subject as a payload in an immutable Ngrx action?

Use case: dispatch an action with a cold observable in the payload.
When an effect catches the action, it subscribes (through mergeMap, switchMap, whatever...) to this observable and send back another action. Classic Ngrx process.
export class ServicesStore {
dispatchObservable(operation$: Observable<unknown>) {
this.store.dispatch(serviceRequestAction({ operation$ }));
}
}
export class ServicesEffects {
serviceRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(serviceRequestAction),
mergeMap((action: ServiceRequestAction) => {
return action.operation$.pipe(
map((result) => {
// send back an action with the result
})
);
})
)
);
}
Usage:
this.servicesStore.dispatch(this.userService.getAll$());
It works well.
But if this observable is a Subject (say MatDialog.open().afterClosed()) it will break the immutable action Ngrx rule.
Because of the inner subscription, the Subject adds an observer into its internal structure, thus breaking the action immutability. It then triggers the Ngrx runtime checks.
Of course I can disable these check, but I am looking for a better away around this. For example, is there a way to clone a Subject ?
Or any other way to allow a Subject into the action payload ?
AFAIK adding a subject to a NgRx Action isn't supported (if you want to keep the runtime checks enabled).
The classic NgRx process is that the effect results in a new action (popular ones are success and failure).

Does applying Redux-Thunk midleware to redux change all actions to be executed asynchronously?

I have a project in which I have applied the redux-thunk middleware to my redux store. Now I have several thunks in my code and this are also being dispatched, but they return ThunkAction<Promise<void>, void, void, AnyAction> so I assume these are async.
In other places of my code I’m directly calling the dispatch method on the store. Does this actions also become asynchronous as an effect of applying middleware or do they remain synchronous?
For example If i do:
store.dispatch(someAction);
Would that still be synchronous?
Thanks in advance.
Yes.
Dispatching is 100% synchronous by default.
If you add middleware, a middleware may alter, delay, or stop an action from progressing through the dispatch pipeline.
However, in this case, the redux-thunk middleware does not do anything asynchronous. It simply checks to see if the "action" is actually a function, and if so, executes it immediately:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
If the thunk middleware is applied, store.dispatch(someThunkFunction) will return whatever the thunk function returns. So, if you return a promise from the thunk, that promise will be returned from store.dispatch().
For the TS types, the first generic argument should indicate the expected return type of the thunk function. However, you would likely actually have to specify that yourself - it shouldn't be appearing randomly.

Firebase React Native fetch data

I am trying to do an app on react-native with a feed. On my main screen, I go fetch the data :
fetchData() {
firebase.database().ref(`/posts/${group}`).on('value', async snapshot => {...}
}
when I want for example to like a comment of the post, I first push the data into firebase with different queries, for example :
export const likeComment = (...) => {
firebase.database().ref(`/posts/${group}/${post}`).update
({
updatedAt: firebase.database.ServerValue.TIMESTAMP
});
firebase.database().ref(`/posts/${group}/${post}/lastComments`).set(...);
But I realized that my first function fetchData was called 3 times.
then I grouped my queries like :
let updates = {}
updates[`/posts/${group}/${post}/lastComments`] = {...};
updates[`/posts/${group}/${post}`] = { ... };
firebase.database().ref().update(updates);
Then, fetchData was called still 2 times.
I was wondering if that was the best way to do it, and why does my function fetchData was still called twice.
Thanks for your help
It's hard to tell from the limited code, but my guess is that fetchData is not actually being called more than once, but instead the snapshot is firing when you make updates to your Firebase database.
The part of your code where you say .on('value', async snapshot => you're setting up a listener to send you updates any time that value changes.
So my guess is that your three invocations are:
The first time you actually call fetchData and set up the
listener
Your .on( snapshot listener firing when you call
update
Your .on( snapshot listener firing again when you
call set
This push-based database workflow is one of the main benefits of Firebase. If you only want to get the data once and not get live updates, you can call .once( instead of .on(
https://firebase.google.com/docs/database/web/read-and-write

How can I know when a redux-saga function has completed in the calling code

React, redux, redux-saga
I dispatch an action, say CREATE_REQUESTED to the store.
Redux-saga runs, and makes the async call to the server.
After the saga completes, i.e. blocks for the next CREATE_REQUESTED I want to execute additional code, from the container/component from which the first CREATE_REQUESTED was initiated.
// pseudo code
class Cmp extends React.component {
onCreateClick(id) {
const record = {id, name: 'alabala'}
// I am missing the .then() part
this.props.dispatch({type: 'CREATE_REQUESTED', record}).then(created => {
console.log(created)
}
}
}
Is there a way to do that? How?
If not, how am I supposed to design this task?
One approach could be to pass (resolve, reject) options along with the action, and make the saga call them on succcess/failure. Seems OK.
https://github.com/yelouafi/redux-saga/issues/161

Resources