Redux Observables: General way to return non-observable in mergeMap? - redux-observable

observable.
In my epic, I just want to call a 3rd party library for scheduling a push notification on iOS (I'm using react native):
import 'rxjs';
import { Observable } from 'rxjs';
import PushNotification from 'react-native-push-notification';
import * as calendarActions from '../ducks/calendar';
export default function cancelRSVPIfSignedIn(action$, store) {
return action$.ofType(calendarActions.CANCEL_RSVP)
.filter(() => store.getState().user.signedIn)
.mergeMap(action => {
return new Observable(observer => {
const meetupId = action.payload;
PushNotification.cancelLocalNotifications({ id: meetupId });
observer.next(meetupId);
});
})
.map(action => calendarActions.rsvpAdded(action.payload));
};
This works fine, but I was wondering if this is the most common approach to return an Observable and inside it just to call observer.next()?

If you need to create an Observable that wraps non-Observable code and you want to observe the results of that code--that's the crucial part.
If you don't care about whether the side effect produces anything, errors, or if it completes asynchronously or not, then wrapping it in a custom Observable isn't neccesary. You could just use the .do() operator instead.
export default function cancelRSVPIfSignedIn(action$, store) {
return action$.ofType(calendarActions.CANCEL_RSVP)
.filter(() => store.getState().user.signedIn)
.do(action => PushNotification.cancelLocalNotifications({ id: action.payload }))
.map(action => calendarActions.rsvpAdded(action.payload));
};
There's one missing thing I want to point out in your code, however. You never call observer.complete() which means you're accidentally leaking the subscription to that custom Observable. Every time a new CANCEL_RSVP comes in another one will be created and subscribed to (mergeMap) with the previous sticking around even though it has no work left to be done.
Remember to always call observer.complete() when you're done, unless of course your Observable intentionally never completes. 😄
(also, it emits observer.next(meetupId) but then later .map(action => but that might just be a typo in this question, not your apps code)

Related

Redux Toolkit: Async Dispatch won't work in react-native

I'm trying to make some async actions with redux toolkit in react-native. The project runs on redux without any issues, beside the implementation issues for createAsyncThunk.
I used the same logic as described in the docs
Within my Slice, I'm creating the createAsyncThunk Object as follows:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(thunkAPI) => {
const state = thunkAPI.getState();
console.log("THUNK state.loggedIn: "+state.loggedIn);
if(state.loggedIn){
return apiHelper.getAddressDataAsync();
}
});
It only differs in the export tag before const tag compared to the docs. I had to make it in order to access the fetchAddressList from outside. The apiHelper.getAddressDataAsync() is an async method, that returns the result of a fetch.
Than I added the extraReducers attribute to my slice object.
export const appDataSlice = createSlice({
name: "appDataReducer",
initialState:{
//Some initial variables.
},
reducers: {
//Reducers...
},
extraReducers: (builder) => {
builder.addCase(fetchAddressList.fulfilled, (state, action) => {
console.log("FULLFILLED::: ",action.payload);
state.addressList = action.payload.addressList;
state.defaultAddressId = action.payload.defaultAddressId;
})
}
});
export const { /*REDUCER_METHOD_NAMES*/ } = appDataSlice.actions;
This slice is stored in the store using configureStore, among other slices, that are definitely working fine.
Calling the fetchAddressList() method using dispatch doesn't do anything:
dispatch(fetchAddressList());
What exactly am I doing wrong here? Would appreciate any hints.
Edit:
Are there configurations required within the configureStore()-method when creating the store object?
This is how I create the store object:
export const store = configureStore({
reducer: {
/*Other reducer objects....,*/
appDataReducer: appDataSlice.reducer
},
});
Maybe something is missing here...
It was due to wrong usage of the createAsyncThunk()-method. I'd passed the thunkAPI to be as the first (and only) parameter to the inner method, which was linked to user arguments passed through parameters into the initial dispatch method (like dispatch(fetchAddressList("ARG_PASSED_TO_FIRST_PARAMETER_OF_ASNYCTHUNK"));). However thunkAPI is being injected into the second parameter of createAsyncThunk()-method and as a result thunkAPI was undefined, since I hadn't passed any parameters by calling dispatch(fetchAddressList());
It was odd, to not have any errors / exceptions
calling a method of an undefined object though => thunkAPI.getState().
The solution is to use the second parameter for thunkAPI.
You do have two options by doing so.
1 - Either load the whole thunkAPI into the second parameter and use it as so:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(args, thunkAPI) => {
console.log("TEST: ", thunkAPI.getState());
thunkAPI.dispatch(...);
});
2 - Or load exported methods by the thunkAPI:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(args,{getState, dispatch}) => {
console.log("TEST: ", getState());
dispatch(...);
});
Both ways will work. Happy coding :)

React-Native - useEffect causes infinite loop

I am trying to show some dynamic content in my component but somehow useEffect causes a infinite loop.
What can be the problem?
useEffect(() => {
retrieveLocalData('following').then((contacts) => {
setLocalData(JSON.parse(contacts));
});
}, [getLocalData]);
async function retrieveLocalData(key) {
try {
return await AsyncStorage.getItem(key);
} catch (error) {
console.log(error);
}
}
console.log('test'); // infinite
Code: https://codepen.io/eneskul/pen/OJWEgmw
Updated Answer
The infinite loop is a result of the useEffect hook updating the same value that is triggering the hook to run in the first place.
Here's a simple example to illustrate the problem:
const [value, setValue] = useState({ foo: 'bar' });
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
const newValue = JSON.parse(result);
// `newValue` is a new object, even if its content is identical to `value`.
setValue(newValue);
});
}, [value]);
In this example, when value is set, it causes the useEffect hook to execute, which will asynchronously update value with a new object, which will cause the useEffect hook to execute again, and so on. Even though the contents of the objects are identical, the JSON.parse call creates a new object with a new reference.
You can prevent the infinite loop by doing a deep equality check of the two objects before updating the state. Using something like Lodash's isEqual function makes this pretty easy.
useEffect(() => {
Promise.resolve('{"foo":"bar"}').then((result) => {
setValue((prev) => {
const newValue = JSON.parse(result);
// Do a deep comparison and only update state with new object if content is different.
return isEqual(prev, newValue) ? prev : newValue;
});
});
}, [value]);
In this example, the reference to value will only change if the contents of the objects are different.
However, this only explains what the problem is. I'm not sure what the right solution is for your problem, since it's not clear why the component only needs to load data from local storage into state when the state changes, but the state is only updated when it loads from local storage. There seems to be a "chicken or the egg" problem here. It feels like there should be something else that should trigger loading data from local storage into state, other than the data that was just loaded from local storage into state.
Previous Answer
The likely culprit here is getLocalData in the dependency list of the useEffect hook. If that is not a stable reference (i.e. the reference changes on each render), then it will cause the useEffect hook to execute, which will then trigger a state update, which will trigger a render, which will cause useEffect to execute again, which starts the whole thing over again.
In the sample code, it's not clear where getLocalData comes from. Wherever it comes from, you might consider wrapping it with the useCallback hook to create a stable reference. If it's just a typo and meant to be retrieveLocalData, then that is definitely the issue. Because retrieveLocalData is declared inside the component's render function, it will create a new instance of the function (with a new reference) on each render.
I would just move it inside the useEffect hook and eliminate the dependencies.
useEffect(() => {
AsyncStorage.getItem('following')
.then((contacts) => {
setLocalData(JSON.parse(contacts));
})
.catch((error) => {
console.log(error);
});
}, []);

Globally Accessible Component Instance

In our production applications with Vue 2.x, we have a toast component. This toast component is mounted once via a plugin (code below) and is then added to the Vue prototype making it accessible in every component instance.
This makes life a lot easier instead of having to add the toast to everywhere we use.
Vue 2.x plugin
export default {
install(vue: any, _: any) {
const root = new Vue({ render: (createElement) => createElement(Toast) });
root.$mount(document.body.appendChild(document.createElement("div")));
const toastInstance: Toast = root.$children[0] as Toast;
vue.prototype.$toast = {
show: (state: ToastState, text: string) => { toastInstance.show(state, text); },
hide: () => { toastInstance.hide(); }
};
}
Which can then be called in any component like:
this.$toast.show(ToastStates.SUCCESS, "Some success message");
I have recently started another project and would like to do something similar, except using Vue 3. Because we don't have access to this in the setup function, I can't use the same approach as before.
I have been looking into a few things, and have found a few ways of doing it, but none as a definitive best practice.
Provide / Inject:
This seems the most promising, where I can use
export const appInstance = createApp(App);
then
appInstance.provide("toast", toastComponentInstance)
which I can then inject in any components. The problem with this, is that to get it available in every component, it needs to be attached to the initial app instance, where it hasn't been created yet. Maybe I could manually mount it and pass it in (but that seems like a hack).
Composition:
I have also looked at this issue here: How to access root context from a composition function in Vue Composition API / Vue 3.0 + TypeScript? but didn't find that very useful and I had to do all types of hacks to actually gain access to the plugin. Gross code below..
export function useToast() {
const root = getCurrentInstance();
const openToast: (options: ToastOptions) => void = (options: ToastOptions) => {
root.ctz.$toast.open(options);
}
const closeToast: () => void = () => {
root.ctx.$toast.close();
}
return {
openToast,
closeToast
}
}
I have other ideas but they seem far fetched an hacky. Keen to hear peoples thoughts on other solutions. I just want a simple way to have 1 instance of a toast, that I can call two functions on to open / close it when and where I want.
This is roughly how I'd do it...
I'd use Composition API, because it makes passing around internals easy
(I'm using popup instead of toast for simplicity)
myPopup.vue
// internal
const popupMessage = Vue.ref('');
const popupVisible = Vue.ref(true);
// external
export const popUpShow = function(message) {
popupMessage.value = message
popupVisible.value = true
}
export const popupHide = function () {
popupVisible.value = false
}
export default {
setup(){
return {
popupMessage, popupVisible, popupHide
}
}
}
Some component, anywhere, composition or class based...
import { popUpShow } from "./myPopup";
export default {
methods: {
myTriggeredEvent() {
popUpShow("I am your Liter")
}
}
}
By exposing popUpShow, which acts as a singleton, you can import that from anywhere, and not have to worry about context.
There the drawback in using this kind of setup/architecture is that it doesn't scale well. The problem happens if your architecture reaches a certain size, and you have multiple triggers coming from various sources that the component needs to have complex logic to handle its state (not likely for this example though). In that case, a managed global store, ie. Vuex, might be a better choice.

How to access dispatch function from epic in redux-observables

I'd like to know if there's anyway to access redux's dispatch function from an epic in redux-observables (1.2).
export const epicDownloadProfile = (action$, { dispatch }) =>
action$.pipe(
ofType(DOWNLOAD_INIT.getType()),
switchMap(() =>
from(downloadStart(dispatch)).pipe(
map(() => DOWNLOAD_INIT()),
catchError(err => of(DOWNLOAD_ERROR.asError(err.message)))
)
)
)
I know this is not ideal, but I have a very complex function that makes a lot of things while downloading, so I'd need to pass dispatch to downloadStart().
Redux-observables provides me with a StateObservable object as the second parameter of the epic, it does contain the state, but it does not contain the dispatch function... In the example { dispatch } comes undefined. Is there any other way I can access it?
You did mention this isn't ideal, but for others who might not read your question I must add a warning that doing this is suggestive that what you might be doing is an anti-pattern--but not always! Certainly if you're using some sort of third party library that you have no control over, and you need to pass it to it, that's an understandable workaround. Just don't be too tempted to called store.dispatch() around your Epics all the time, as it is a usually a sign you're fighting redux-observable. Of course, at the end of the day, this is just advice hehe :)
OK. So here's how you can do it:
redux-observable provides a way to inject dependencies into every epic. So when you create your epicMiddleware, you can pass a reference to the store, dispatch, or anything else.
https://redux-observable.js.org/docs/recipes/InjectingDependenciesIntoEpics.html
/* Where ever you create your store/middleware
*****************************************/
const middlewares = [];
const epicMiddleware = createEpicMiddleware({
dependencies: {
get store() { // or getStore() if you want
return store;
}
}
});
middlewares.push(applyMiddleware(epicMiddleware));
const store = createStore(
rootReducer,
initialState,
composeEnhancers(...middlewares)
);
epicMiddleware.run(rootEpic);
/* Where ever this epic is
*************************/
const epicDownloadProfile = (action$, state$, { store }) =>
action$.pipe( dependencies ----^
ofType(DOWNLOAD_INIT.getType()),
switchMap(() =>
from(downloadStart(store.dispatch)).pipe(
map(() => DOWNLOAD_INIT()),
catchError((err) => of(DOWNLOAD_ERROR.asError(err.message)))
)
)
);
There are other approaches too, such as exporting your store from the module, importing it inside your epic modules. But that might not be good if you need to don't want your store to be a singleton, doing SSR, etc.
Here's another approach, if you prefer it, since you should always start the root epic after the store has been created anyway.
// Manually inject it yourself by wrapping the "root epic"
// with another function, which is basically an epic which
// defers to your root epic.
epicMiddleware.run((action$, state$) => {
return rootEpic(action$, state$, { store });
});

Subscribe to single property change in store in Redux

In Redux I can easily subscribe to store changes with
store.subscribe(() => my handler goes here)
But what if my store is full of different objects and in a particular place in my app I want to subscribe to changes made only in a specific object in the store?
There is no way to subscribe to part of the store when using subscribe directly, but as the creator of Redux says himself - don't use subscribe directly! For the data flow of a Redux app to really work, you will want one component that wraps your entire app. This component will subscribe to your store. The rest of your components will be children to this wrapper component and will only get the parts of the state that they need.
If you are using Redux with React then there is good news - the official react-redux package takes care of this for you! It provides that wrapper component, called a <Provider />. You will then have at least one "smart component" that listens to state changes passed down by the Provider from the store. You can specify which parts of the state it should listen to, and those pieces of the state will be passed down as props to that component (and then of course, it can pass those down to its own children). You can specify that by using the connect() function on your "smart" component and using the mapStateToPropsfunction as a first parameter. To recap:
Wrap root component with Provider component that subscribes to store changes
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Now any child of <App /> that is wrapped with connect() will be a "smart" component. You can pass in mapStateToProps to pick certain parts of the state and give it those as props.
const mapStateToProps = (state) => {
return {
somethingFromStore: state.somethingFromStore
}
}
class ChildOfApp extends Component {
render() {
return <div>{this.props.somethingFromStore}</div>
}
}
//wrap App in connect and pass in mapStateToProps
export default connect(mapStateToProps)(ChildOfApp)
Obviously <App /> can have many children and you can pick and choose which parts of the state the mapStateToProps should listen to for each of its children. I'd suggest reading the docs on usage with React to get a better understanding of this flow.
Redux only offers a single generic way to know when the store has updated: the subscribe method. Callbacks to subscribe do not get any info on what might have changed, as the subscribe API is deliberately low-level, and simply runs each callback with no arguments. All you know is that the store has updated in some way.
Because of that, someone has to write specific logic to compare old state vs new state, and see if anything has changed. You could handle this by using React-Redux, specifying a mapStateToProps function for your component, implementing componentWillReceiveProps in your component, and checking to see if specific props from the store have changed.
There are also a couple addon libraries that try to handle this case: https://github.com/ashaffer/redux-subscribe and https://github.com/jprichardson/redux-watch . Both basically let you specify a specific portion of the state to look at, using different approaches.
In addition to what Andy Noelker said, mapStateToProps not only passes part of the state properly down your component tree, it also subscribes to changes made directly in these subscribed portions of the state.
It is true that every mapStateToProp function you bind to the store gets called each time any part of the state is changed, but the result of the call gets shallow compared to the previous call - if top level keys you subscribed onto did not change (the reference stays the same). Then mapStateToProps would not call re-render. So if you want the concept to work, you have to keep mapStateToProps simple, no merging, type changing or anything, they should simply pass down parts of the state.
If you want to reduce the data from the state when subscribing, for example you had list data in the state and you want to convert it to object with ids as keys, or you want to join multiple states into data structures, you should combine mapStateToProps with createSelector from reselect library, by doing all these modifications inside selector. Selectors are pure functions that reduce and cache state chunks passed in as input and if input did not change - they return exactly the same reference they did on the last call - without performing the reduction.
Created a hack to help understand the subscribers can be differentiated based on store data, with multiple store capability.
//import { createStore } from 'redux';
let createStore = require('redux').createStore;
let combineReducers = require('redux').combineReducers;
/**
* This is a reducer, a pure function with (state, action) => state signature.
* It describes how an action transforms the state into the next state.
*
* The shape of the state is up to you: it can be a primitive, an array, an object,
* or even an Immutable.js data structure. The only important part is that you should
* not mutate the state object, but return a new object if the state changes.
*
* In this example, we use a `switch` statement and strings, but you can use a helper that
* follows a different convention (such as function maps) if it makes sense for your
* project.
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function messanger(state = 'Mr, khazi', action) {
switch(action.type) {
case 'WELCOME':
return 'Hello, Mr Khazi';
case 'BYE':
return 'Bye, Mr Khazi';
case 'INCREMENT':
return 'Incremented khazi';
default:
return state;
}
};
function latestAction(state = null, action) {
switch(action.type) {
case 'WELCOME':
return '$messanger';
case 'BYE':
return '$messanger';
case 'INCREMENT':
return '$messanger, $counter';
case 'DECREMENT':
return '$counter';
default:
return state;
}
};
let reducers = {
counts: counter,
message: messanger,
action: latestAction
};
let store = createStore(
combineReducers(reducers, latestAction)
);
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
//let store = createStore(counter)
// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// However it can also be handy to persist the current state in the localStorage.
store.subscribe(() => {
if(store.getState().action.indexOf('messanger') !== -1) {
console.log('subscribed for counter actions', store.getState());
}
});
store.subscribe(() => {
if (store.getState().action.indexOf('counter') !== -1) {
console.log('subscribed for messanger actions', store.getState());
}
});
// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
console.log('----------------Action with both subscriber-------------');
store.dispatch({ type: 'INCREMENT' });
console.log('---------------Action with counter subscriber-----------');
store.dispatch({ type: 'DECREMENT' });
console.log('---------------Action with messenger subscriber---------');
store.dispatch({ type: 'WELCOME' });
/*
every reducer will execute on each action.
*/