Alternative to direct calling of store.dispatch() - redux

Since in the latest redux-observable (0.17) is direct calling of store.dispatch() deprecated, I wonder what is an alternative if I need to dispatch actions from the outside of my redux app.
Example:
Let's say I have this function which initialize native module and set up native handler.
const configure = (dispatch) => {
const printingModule = NativeModules.PrintingManager
const eventEmitter = new NativeEventEmitter(printingModule)
eventEmitter.addListener(
"PrintingManagerNewPrinterConnected",
(payload) => dispatch({
type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
payload: {
macAddress: payload[2],
connectionType: payload[3],
},
}))
printingModule.initialize()
}
What I typically do is that I will call this function from observable after something like APP_STARTUP_FINISHED:
const appStatePrepared = (action$: Object, { dispatch }) =>
action$.ofType(APP_STATE_PREPARED)
.switchMap(() => {
configurePrinters(dispatch)
})
What is the correct solution for this?
Thanks!

When using RxJS the ideal is to compose streams. So in this case we need to some how create a stream of "PrintingManagerNewPrinterConnected" events that we can then map each to their own PRINTER_MANAGER_NEW_PRINTER_CONNECTED action.a
Let's first learn how to do this completely custom.
Custom Observables
Creating your own custom Observables is very similar to creating a Promise. So say you had the most simple Promise in the world that just immediately resolves to the number 1
const items = new Promise(resolve => {
resolve(1);
});
The equivalent Observable looks super similar
const items = new Observable(observer => {
observer.next(1);
observer.complete();
});
Visually, the main differences are that instead of being passed (resolve, reject) callbacks we're given an Observer because there is next, error, and complete.
Semantically, Observables can represent more than one value by calling observer.next as many times as they'd like until they call observer.complete() to signal the end of the stream; this is in contrast to Promises, which only represent a single value.
Observables are also lazy and synchronous by default, whereas Promises are always eager and async.
Now that we have that understanding we want to take that and wrap your NativeEventEmitter API which uses addEventListener.
const configurePrinters = () => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
eventEmitter.addListener(
'PrintingManagerNewPrinterConnected',
(payload) => observer.next(payload)
);
printingModule.initialize();
});
};
configurePrinters()
.subscribe(payload => console.log(payload));
This works and is super simple, but there's one problem with it: we should call removeListener when they unsubscribe so that we clean up after ourselves and don't leak memory.
To do that, we need to return a subscription inside our custom Observable. A subscription in this context is an object that has an unsubscribe() method on it, which will be called automatically when the subscriber unsubscribes, an error is triggered, or your observable completes. This is your chance to clean up.
const items = new Observable(observer => {
let i = 0;
const timer = setInterval(() => {
observer.next(i++);
}, 1000);
// return a subscription that has our timer cleanup logic
return {
unsubscribe: () => {
clearInterval(timer);
}
};
});
Because returning an object is a bit verbose RxJS supports a shorthand where you just return a function which itself will be treated as the unsubscribe method.
const items = new Observable(observer => {
let i = 0;
const timer = setInterval(() => {
observer.next(i++);
}, 1000);
// return an "unsubscribe" function that has our timer cleanup logic
return () => {
clearInterval(timer);
};
});
Now we can apply this to our example, where we want to remove our listener when our unsubscribe teardown function is called.
const configurePrinters = () => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
const listener = (payload) => observer.next(payload);
eventEmitter.addListener(
'PrintingManagerNewPrinterConnected',
listener
);
printingModule.initialize();
return () => eventEmitter.removeListener(
'PrintingManagerNewPrinterConnected',
listener
);
});
};
Now let's turn this into a reusable utility function
const fromPrinterEvent = (eventName) => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
const listener = (payload) => observer.next(payload);
eventEmitter.addListener(eventName, listener);
printingModule.initialize();
return () => eventEmitter.removeListener(eventName, listener);
});
};
fromPrinterEvent('PrintingManagerNewPrinterConnected')
.subscribe(payload => console.log(payload));
Observable.fromEvent
While NativeEventEmitter is a react-native thing, it follows the node-style EventEmitter interface and RxJS already comes with a utility helper to create an Observable from them to save you the effort. It's called fromEvent, found at Observable.fromEvent or import { fromEvent } from 'rxjs/observables/fromEvent'.
const fromPrinterEvent = (eventName) => {
return Observable.defer(() => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
printingModule.initialize();
return Observable.fromEvent(eventEmitter, eventName);
});
};
Here I also wrapped it in Observable.defer so that we don't create the NativeEventEmitter or printingModule.initialize() until someone actually subscribes (maintain laziness). This may or may not be neccesary for you, I don't know what PrintingManager does or how it behaves. e.g. it might be desirable to only create a single emitter and to initialize the module upfront.
const printingModule = NativeModules.PrintingManager;
const printerEmitter = new NativeEventEmitter(printingModule);
printingModule.initialize();
const fromPrinterEvent = (eventName) =>
Observable.fromEvent(printerEmitter, eventName);
So keep in mind that I'm just showing patterns without knowing what PrintingManager, etc does.
Use it within redux-observable
To use this within redux-observable and your epic is now the same as you'd use any other Observable. So we'll want to map the values from it to actions and
mergeMap, switchMap, concatMap, or exhaustMap that into our top-level stream.
Something like this:
const appStatePrepared = action$ =>
action$.ofType(APP_STATE_PREPARED)
.switchMap(() =>
fromPrinterEvent('PrintingManagerNewPrinterConnected')
.map(payload => ({
type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
payload: {
macAddress: payload[2],
connectionType: payload[3],
}
}))
);
Remember that many streams, including our custom fromPrinterEvent('PrintingManagerNewPrinterConnected'), go forever until you unsubscribe from them. So if you only want one you'd use .take(1). If you want to unsubscribe when you receive another action you'd use .takeUntil(action$.ofType(WHATEVER)), etc. Normal RxJS patterns.

Related

React Native state console.log

I am using firebase for my app and the data i read i want to put that in state to use it in different places.
it kinda works but when i want to console.log the state it updates like 30 times a second, am i doing something wrong?
this is my code
const db = firebase.firestore();
const [PBS1Detail, setPBS1Detail] = useState();
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
return setPBS1Detail(data);
}
console.log(PBS1Detail)
i already tried to store it in a variable instead of state but thats not working for me, i can't get the variable from the function global
i am a noob i get it xd
You don't need a return statement when setting state. Also, it looks like you're performing some async function which means that when your component renders for the first time, PBS1Detail will be undefined since the db is a pending Promise.
You could/should put your async functions in useEffect hook:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
snapshot.docs.forEach(doc => {
renderTracks(doc)
}
)
});
}, [])
const renderTracks = (doc) => {
let data = doc.data().data[0].Module;
setPBS1Detail(data);
}
Finally, your renderTracks function doesn't seem correct as it appears you're looping over docs and resetting your state each time.
Instead, maybe consider having an array for PBS1Detail
const [PBS1Detail, setPBS1Detail] = useState([]);
Then modify your async call:
useEffect(()=> {
db.collection('Track').get().then((snapshot) => {
let results = []
snapshot.docs.forEach(doc => {
results.push(renderTracks(doc))
}
)
setPBS1Detail(results)
});
}, [])
const renderTracks = (doc) => {
return doc.data().data[0].Module;
}
This way you're only setting state once and thus avoiding unnecessary re-renders and you're saving all of your docs instead of overwriting them.

dispatch is not a function Next.js +thunk load data user after login

When I'm try to getUserProfile() I receive that typeError that dispatch is not a function
Unhandled Runtime Error
Error: Actions must be plain objects. Use custom middleware for async actions.
export const fetchUserProfile = (userData) => ({
type: types.GET_USER_PROFILE,
userData,
});
//thunk
export const loginUser = (credentials) => async (dispatch) => {
dispatch(loginRequest(credentials));
try {
const userToken = await userService.login(credentials);
await dispatch(loginSuccess(userToken));
getUserProfile();
} catch (error) {
const message = await errorMessage(
error,
"There was a problem processing your request"
);
dispatch(loginFailure(message));
}
};
export const getUserProfile = async (dispatch) => {
try {
const profileData = await userService.getProfileData();
dispatch(fetchUserProfile(profileData));
} catch (e) {
console.log(e);
return [];
}
};
You need to dispatch all thunks, replace
getUserProfile();
with
dispatch(getUserProfile())
Your getUserProfile should be a function that accepts its own arguments when you dispatch it, and then it can either be a callback function that accepts dispatch as an argument (this comes from the Redux Thunk middleware) and then that function has functions that return action objects, OR it can just be a function that returns an action object directly (confusing, I know, but you actually did it correctly for your loginUser action):
export const getUserProfile = () => async (dispatch) => {
try {
const profileData = await userService.getProfileData();
dispatch(fetchUserProfile(profileData));
} catch (e) {
console.log(e);
return []; // this shouldn’t be returning an empty array, if anything it should be dispatching an action for errors that can be displayed to the user
}
};
This overly simple example kind of gives you an idea of what's happening (click Run code snippet):
// the "dispatch" function that would come from
// the Redux Thunk middleware
const dispatch = (action) => action((args) => console.log("dispatch:", JSON.stringify(args, null, 2)));
// a "setUserProfile" action that returns an object
const setUserProfile = (payload) => ({
type: "SET_PROFILE",
payload
});
// a "fetchUserProfile" action that returns an object
const fetchUserProfile = () => ({ type: "FETCH_USER" });
// a "showError" action that returns an object
const showError = error => ({ type: "FETCH_USER/ERROR", payload: error });
// while the "getUserProfile" action doesn't have any arguments of
// its own, it accepts a "dispatch" callback function as the SECOND
// set of arguments, then other actions are dispatched (which return
// their own objects)
const getUserProfile = () => async(dispatch) => {
try {
// dispatches the "fetchUserProfile" action
// which just returns: { type: "FETCH_USER" }
dispatch(fetchUserProfile());
// fetching data from API
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
const data = await res.json();
// dispatches the "setUserProfile" with data from API
// which returns: { type: "SET_PROFILE", payload: data }
dispatch(setUserProfile(data));
} catch (e) {
console.log(e);
dispatch(showError(e.message));
}
};
// dispatching the "getUserProfile" function above
// optionally, you can add arguments here, but then these would be
// a part of the FIRST set of arguments to the function
dispatch(getUserProfile());
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>

A clean way for an action to fire multiple asynchronous actions with createAsyncThunk

We're delaying the rendering of our React-Redux web app until several asynchronous app initialization tasks in the Redux store have been completed.
Here's the code that sets up the store and then fires off the initialization action:
export const setupStoreAsync = () => {
return new Promise((resolve, reject) => {
const store = setupStore()
store
.dispatch(fetchAppInitialization())
.then(unwrapResult)
.then(_ => resolve(store))
.catch(e => reject(e.message))
})
}
The promise rejection is very important since it's used to render an error message for the user in case the app cannot be properly set up. This code is very nice to read and works wonderfully.
The issue is with the action creator:
export const fetchAppInitialization = createAsyncThunk(
'app/initialization',
(_, thunkApi) =>
new Promise((resolve, reject) =>
Promise.all([thunkApi.dispatch(fetchVersionInfo())]).then(results => {
results.map(result => result.action.error && reject(result.error))
})
)
)
This code works beautifully. If any of these actions fail, the promise is rejected and the user sees an error message. But it's ugly - It's not as pretty as our normal action creators:
export const fetchVersionInfo = createAction('system/versionInfo', _ => ({
payload: {
request: { url: `/system/versionInfo` },
},
}))
We will at some point fire more than one fetch request in fetchAppInitialization, so the Promise.all function is definitely required. We'd love to be able to use Redux-Toolkit's createAction syntax to fire multiple promisified actions in order to shorten this action creator, but I have no idea if that's even possible.
Note: I'm using redux-requests to handle my axios requests.
Is createAsyncThunk even required?
Since I wasn't using the fetchAppInitialization action for anything but this single use case, I've simply removed it and moved the logic straight into the setupStoreAsync function. This is a bit more compact. It's not optimal, since the results.map logic is still included, but at least we don't use createAsyncThunk any more.
export const setupStoreAsync = () => {
return new Promise((resolve, reject) => {
const store = setupStore()
new Promise((resolve, reject) =>
Promise.all([store.dispatch(fetchVersionInfo())]).then(results => {
results.map(result => result.action.error && reject(result.error))
resolve()
})
)
.then(_ => resolve(store))
.catch(e => reject(e.message))
})
}
Update: I was able to make the code even prettier by using async/await.
export const setupStoreAsync = async () => {
const store = setupStore()
const results = await Promise.all([store.dispatch(fetchVersionInfo())])
results.forEach(result => {
if (result.action.error) throw result.error
})
return store
}

redux-thunk and in app architecture - want to render only views in views and dispatch GET actions in separate component

I am using react-redux and redux-thunk in my application and there are two things I am trying to do:
I want to be able to share the results of a GET request in two components. I know you can do this by connecting the two components to the store, but I want to make it so if the user lands on X page, then Y page cannot make the same GET request again (these two components are Thumbnail and Carousel). In other words, the GET request should be made once (not 100% sure what best practice is here for redux-thunk), and each component should be able to access the store and render the results in the component (this is easy and I can do)
currently the GET request is the parent of the two children view components, which (I think) doesn't make sense. I only want to render a child view component in the parent view, not a GET request. If unclear it will make more sense if you read my code below
This is parent view (Gallery), which has a child component which dispatches an action to redux (using redux-thunk) that makes an API (FetchImages):
import ...
export default function Gallery() {
return(
<>
<GalleryTabs />
<GalleryText />
<div className="gallery-images-container">
<FetchImages /> ----> this is making an API request and rendering two child view components
</div>
</>
)
}
This is FetchImages, which is dispatching the action (fetchImages) which makes the API call
import ...
function FetchImages({ fetchImages, imageData }) {
useEffect(() => {
fetchImages()
}, [])
return imageData.loading ? (
<h2>Loading</h2>
) : imageData.error ? (
<h2>Something went wrong {imageData.error}</h2>
) : (
<>
<Thumbnail /> -----> these two are views that are rendered if GET request is successful
<Carousel />
</>
)
}
const mapStateToProps = state => {
return {
imageData: state.images
}
}
const mapDispatchToProps = dispatch => {
return {
fetchImages: () => dispatch(fetchImages())
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(FetchImages)
I think it makes more sense to have something like this:
import ...
export default function Gallery() {
return(
<>
<GalleryTabs />
<GalleryText />
<div className="gallery-images-container">
<Thumbnail /> -----> Thumbnail should be rendered here but not Carousel ( FetchImages here adds unnecessary complexity )
</div>
</>
)
}
tldr
What are some best practices to follow if two components can dispatch an action which makes a GET request but the dispatch should only be made once per time the user is on the website?
Using redux-thunk, what are some best practices for separating concerns so that children view components are within parent view components and the smarter components which are shared between children view components (such as dispatching actions that make GET requests) are dispatched when the user lands on the page without the views and smarter components being directly together?
I'm a noob so thank you for any help
Your first question: your component container should just dispatch the action that it needs data. How you should store async result in state and later handle result from state is something not covered in this answer but the later example uses a component named List that just dispatches getting a data page, selects the data page and dumps the data page in UI. The tunk action does an early return if the data is already in state.
In production application you probably want to store async api result with loading, error, requested and a bunch of extra info instead of assuming it is there or not there.
Your second question is partly answered by the first answer. Component containers should just dispatch an action indicating they need data and not have to know about the data already being there, already being requested or any of that stuff.
You can group functions that return a promise with the following code:
//resolves a promise later
const later = (time, result) =>
new Promise((resolve) =>
setTimeout(() => resolve(result), time)
);
//group promise returning function
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.resolved(key); //tell cache promise is done
return r;
},
(e) => {
cache.resolve(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//permanent memory cache store creator
const createPermanentMemoryCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
resolved: (x) => x,//will not remove cache entry after promise resolves
};
};
//temporary memory cache store creator when the promise is done
// the cache key is removed
const createTmpMemCache = () => {
const map = new Map();
const cache = createPermanentMemoryCache(map);
cache.resolved = (key) => map.delete(key);
return cache;
};
//tesgting function that returns a promise
const testPromise = (m) => {
console.log(`test promise was called with ${m}`);
return later(500, m);
};
const permanentCache = createPermanentMemoryCache();
const groupTestPromise = createGroup(permanentCache)(
testPromise,
//note that this causes all calls to the grouped function to
// be stored under the key 'p' no matter what the arguments
// passed are. In the later List example I leave this out
// and calls with different arguments are saved differently
() => 'p'
);
Promise.all([
//this uses a permanent cache where all calls to the function
// are saved under the same key so the testPromise function
// is only called once
groupTestPromise('p1'),//this creates one promise that's used
// in all other calls
groupTestPromise('p2'),
])
.then((result) => {
console.log('first result:', result);
return Promise.all([
//testPromise function is not called again after first calls
// resolve because cache key is not removed after resolving
// these calls just return the same promises that
// groupTestPromise('p1') returned
groupTestPromise('p3'),
groupTestPromise('p4'),
]);
})
.then((result) => console.log('second result', result));
const tmpCache = createTmpMemCache();
const tmpGroupTestPromise = createGroup(tmpCache)(
testPromise,
//all calls to testPromise are saved with the same key
// no matter what arguments are passed
() => 'p'
);
Promise.all([
//this uses a temporary cache where all calls to the function
// are saved under the same key so the testPromise function
// is called twice, the t2 call returns the promise that was
// created with the t1 call because arguments are not used
// to save results
tmpGroupTestPromise('t1'),//called once here
tmpGroupTestPromise('t2'),//not called here using result of t1
])
.then((result) => {
console.log('tmp first result:', result);
return Promise.all([
//called once here with t3 becuase cache key is removed
// when promise resolves
tmpGroupTestPromise('t3'),
tmpGroupTestPromise('t4'),//result of t3 is returned
]);
})
.then((result) =>
console.log('tmp second result', result)
);
const tmpUniqueKeyForArg = createGroup(createTmpMemCache())(
testPromise
//no key function passed, this means cache key is created
// based on passed arguments
);
Promise.all([
//this uses a temporary cache where all calls to the function
// are saved under key based on arguments
tmpUniqueKeyForArg('u1'), //called here
tmpUniqueKeyForArg('u2'), //called here (u2 is different argument)
tmpUniqueKeyForArg('u1'), //not called here (already called with u1)
tmpUniqueKeyForArg('u2'), //not called here (already called with u2)
])
.then((result) => {
console.log('unique first result:', result);
return Promise.all([
tmpUniqueKeyForArg('u1'), //called with u1 tmp cache removes key
// after promise is done
tmpUniqueKeyForArg('u3'), //called with u3
tmpUniqueKeyForArg('u3'), //not called, same argument
]);
})
.then((result) =>
console.log('unique second result', result)
);
Now that we have code to group functions that return promises (function is not called when called again with same argument) we can try to apply this to thunk action creators.
Because a trunk action creator is not (...args)=>result but (...args)=>(dispatch,getState)=>result we can't pass the action creator directly to createGroup I created createGroupedThunkAction that adopts the function to group from (...args)=>(dispatch,getState)=>result to ([args],dispatch,getState)=>result while still returning a function with the right signature: (...args)=>(dispatch,getState)=>result.
Here is the example snippet:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
//resolves a promise later
const later = (time, result) =>
new Promise((resolve) =>
setTimeout(() => resolve(result), time)
);
//group promise returning function
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.resolved(key); //tell cache promise is done
return r;
},
(e) => {
cache.resolve(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//thunk action creators are not (...args)=>result but
// (...args)=>(dispatch,getState)=>result
// so here is how we group thunk actions
const createGroupedThunkAction = (thunkAction, cache) => {
const group = createGroup(
cache
)((args, dispatch, getState) =>
thunkAction.apply(null, args)(dispatch, getState)
);
return (...args) => (dispatch, getState) => {
return group(args, dispatch, getState);
};
};
//permanent memory cache store creator
const createPermanentMemoryCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
resolved: (x) => x,//will not remove cache entry after promise is done
};
};
const initialState = {
data: {},
};
//action types
const MAKE_REQUEST = 'MAKE_REQUEST';
const SET_DATA = 'SET_DATA';
//action creators
const setData = (data, page) => ({
type: SET_DATA,
payload: { data, page },
});
const makeRequest = (page) => ({
type: MAKE_REQUEST,
payload: page,
});
//standard thunk action returning a promise
const getData = (page) => (dispatch, getState) => {
console.log('get data called with page:',page);
if (createSelectDataPage(page)(getState())) {
return; //do nothing if data is there
}
//return a promise before dispatching anything
return Promise.resolve()
.then(
() => dispatch(makeRequest(page)) //only once
)
.then(() =>
later(
500,
[1, 2, 3, 4, 5, 6].slice(
(page - 1) * 3,
(page - 1) * 3 + 3
)
)
)
.then((data) => dispatch(setData(data, page)));
};
//getData thunk action as a grouped function
const groupedGetData = createGroupedThunkAction(
getData,//no getKey function so arguments are used as cache key
createPermanentMemoryCache()
);
const reducer = (state, { type, payload }) => {
console.log('action:', JSON.stringify({ type, payload }));
if (type === SET_DATA) {
const { data, page } = payload;
return {
...state,
data: { ...state.data, [page]: data },
};
}
return state;
};
//selectors
const selectData = (state) => state.data;
const createSelectDataPage = (page) =>
createSelector([selectData], (data) => data[page]);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
//improvided thunk middlere
({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
)
)
);
//List is a pure component using React.memo
const List = React.memo(function ListComponent({ page }) {
const selectDataPage = React.useMemo(
() => createSelectDataPage(page),
[page]
);
const data = useSelector(selectDataPage);
const dispatch = useDispatch();
React.useEffect(() => {
if (!data) {
dispatch(groupedGetData(page));
}
}, [data, dispatch, page]);
return (
<div>
<pre>{data}</pre>
</div>
);
});
const App = () => (
<div>
<List page={1} />
<List page={1} />
<List page={2} />
<List page={2} />
</div>
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
In that example there are 4 List components rendered, two for page 1 and two for page 2. All 4 will dispatch groupedGetData(page) but if you check the redux dev tools (or the console) you see MAKE_REQUEST and resulting SET_DATA is only dispatched twice (once for page 1 and once for page 2)
Relevant grouping functions with permanent memory cache is less than 50 lines and can be found here

Cloud Function keeps running

We have a Firebase function which removes items after 24 hours. The function works, however, the web page linking to the function keeps running so I guess the function is in an endless loop. Is there any way to fix this issue?
This is our function:
exports.delete = functions.https.onRequest((req, res) => {
const currentTime = new Date().getTime();
const dayAgo = currentTime - 86400000;
ref.child('testlijst')
.orderByChild('tijd')
.endAt(dayAgo)
.once('value')
.then(snap => {
snap.forEach(c => {
c.ref.remove();
})
});
});
You are required to send a response to the client in order to terminate an HTTPS type function without timing out. You should do that only after the work you're doing in the function is complete. This means you'll need to learn how to use promises.
You will need to collect all the promises returned by c.ref.remove() and wait for all of them to complete before terminating the function with a response to the client:
ref.child('testlijst')
.orderByChild('tijd')
.endAt(dayAgo)
.once('value')
.then(snap => {
const promises = [];
snap.forEach(c => {
const p = c.ref.remove();
promises.push(p);
})
Promise.all(promises).then(result => {
res.send("done");
});
});
});

Resources