How to Chain Dynamic Series of Async Actions Using Redux Thunk? - reactjs

The Norm
According to the official documentation (https://github.com/gaearon/redux-thunk), I know that redux thunk allows dispatching a chain of async actions sequentially like so:
function makeSandwichesForEverybody() {
return function (dispatch, getState) {
return dispatch(
makeASandwichWithSecretSauce('My Grandma')
).then(() =>
Promise.all([
dispatch(makeASandwichWithSecretSauce('Me')),
dispatch(makeASandwichWithSecretSauce('My wife'))
])
).then(() =>
dispatch(makeASandwichWithSecretSauce('Our kids'))
).then(() =>
dispatch(getState().myMoney > 42 ?
withdrawMoney(42) :
apologize('Me', 'The Sandwich Shop')
)
);
}
}
My Situation
However, what if I have a dynamic array of actions that I want to iterate through and call?
let arrOfActions = [];
arrOfActions.push(action1);
arrOfActions.push(action2);
arrOfActions.push(action3);
How can I chain these async actions iteratively using Promise logic? To best explain what I am thinking, I am hoping to do something like this:
function thunkActionCreator() {
return function (dispatch, getState) {
for (let key of arrOfActions) {
dispatch(arrOfActions[key]()).then(
// run next action in arrOfActions here
)
}
}
}
Is this dynamic iteration of function calls possible? If so, what is the syntax?
For verification that you can indeed call functions in an array of functions, here is the resource I found: How to store functions into an array and loop through each in javascript
Why A Dynamic Array of Actions?
There may be a better way to think of this, but the reason I am trying to use this implementation is because I have a series of functions that I need to call in a specific order. This array would be stored in Redux's store, and I am not sure how else to be able to call a series of functions in order from start to finish. Any other ideas would be helpful!

Upfront disclaimer; I think the fact that you need to do this is evidence of deeper problems in your codebase. You really shouldn't be queueing up a list of async functions that need to occur in a specific order and which you don't know of in advanced. That's a number of red flags.
But can you do it? Sure!
function enqueueDynamicArray(functionArray) {
let p = Promise.resolve();
for(index in functionArray) {
p = p.then(functionArray[index]);
}
return p;
}
EDIT: And per comments, if you can rely on the functions being synchronous;
function callDynamicArray(functionArray) {
for(index in functionArray){
functionArray[index]();
};
}

Related

react: helping to understand specific currying use case

<Select
onChange={evt => myFunction('KEY', ['ARRAY', 'OF', 'VALUES'])(evt)}
...
const myFunction = (key, funValues) => {
return (evt: React.ChangeEvent<HTMLSelectElement>) => {
const { values } = evt.target;
if (funValues.find( some condition) ){
callAPI(key, funValues);
}
else{
callAPI(key, values);
}
};
};
I would have written this simply as
onChange={evt => myFunction('KEY', ['ARRAY', 'OF', 'VALUES'], evt)}
I am really failing to see what was the logic of applying currying here, and how it will make
a) this operation better
and/or
b) contributes to broader benefits
Maybe this helps with context, but myFunction is called throughout the codebase. Sometimes via an evt, and sometimes manually.
Creating a curried function (I guess) is a misunderstanding for avoiding an arrow function in the callback handler. In order to this misunderstanding some people define this function as a curried one and use such as:
onChange={myFunction('KEY', ['ARRAY', 'OF', 'VALUES'])}
As you can see this is somehow shorter than your version and works. So, you don't have to use an arrow function and invoke it as you do. event is passed again.
Some people think that this avoids a recreation of this function in each render, but that is not true.

react native calling a fat arrow function after setState: function is not defined

On button pressed my App calls the handlePress function, which returns random objects from an array.
handlePress function:
handlePress = () => {
this.setState({
vitamin: getRandomIngredient(vitaminArray),
}, ()=> matchMealPreference())
}
If I replace matchMealPreference() with a console.log() it works just fine.
After setting the new states I want to call another function immediately with a fat arrow. Otherwise I run into async problems.
The matchMealPreference function looks like this:
matchMealPreference = () => {
if(this.props.mealPreference === this.state.protein.getIngredientFlag()){
return state
} else {
handlePress()
}
}
The function to get a random object from an array:
function getRandomIngredient (arr){
if (arr && arr.length) {
return arr[Math.floor(Math.random() * arr.length)];
}
}
I get the error:
reference error. matchMealPreference is not defined
I'm almost certain that I'm missing something trivial. But I've been stuck at that problem for over a day now, so I thought I'd turn to you for help.
Is there another way to call the matchMealPrefence without the asynchronous problems occuring?
Is it at all possible to call a function at the position where matchMealPreference is called?
Is it unwise to call the handlePress function within the matchMealPrefence function again?
Any help is much appreciated
Edit: Vijay Menon's answer was correct. I needed to add 'this'. Thanks!
You have to reference "matchMealPreference" with "this" keyword inside setState. You would have to do the same for calling "handlePress" inside "matchMealPreference" function
https://codesandbox.io/s/542919430l
changeStr = () => {
this.setState({str:"Changed"})
}
click = () => {
this.setState({num:this.state.num+1},() => {
this.changeStr()
})
}

How does React Hooks useCallback "freezes" the closure?

I'd like to know how does React "freezes" the closure while using the useCallback hook (and with others as well), and then only updates variables used inside the hook when you pass them into the inputs parameter.
I understand that the "freeze" may not be very clear, so I created a REPL.it that shows what I mean: https://repl.it/repls/RudeMintcreamShoutcast. Once you open the code, open your web browser console and start clicking on the count button.
How come the value outside compared to the one inside, for the same variable, is different, if they're under the same closure and referencing the same thing? I'm not familiar with React codebase and so I suppose I'm missing an under the hood implementation detail here, but I tried to think how that could work for several minutes but couldn't come up with a good understanding on how React is achieving that.
The first time the component is rendered, the useCallback hook will take the function that is passed as its argument and stores it behind the scenes. When you call the callback, it will call your function. So far, so good.
The second time that the component is rendered, the useCallback hook will check the dependencies you passed in. If they have not changed, the function you pass in is totally ignored! When you call the callback, it will call the function you passed in on the first render, which still references the same values from that point in time. This has nothing to do with the values you passed in as dependencies - it's just normal JavaScript closures!
When the dependencies change, the useCallback hook will take the function you pass in and replace the function it has stored. When you call the callback, it will call the new version of the function.
So in other words, there's no "frozen"/conditionally updated variables - it's just storing a function and then re-using it, nothing more fancy than that :)
EDIT: Here's an example that demonstrates what's going on in pure JavaScript:
// React has some component-local storage that it tracks behind the scenes.
// useState and useCallback both hook into this.
//
// Imagine there's a 'storage' variable for every instance of your
// component.
const storage = {};
function useState(init) {
if (storage.data === undefined) {
storage.data = init;
}
return [storage.data, (value) => storage.data = value];
}
function useCallback(fn) {
// The real version would check dependencies here, but since our callback
// should only update on the first render, this will suffice.
if (storage.callback === undefined) {
storage.callback = fn;
}
return storage.callback;
}
function MyComponent() {
const [data, setData] = useState(0);
const callback = useCallback(() => data);
// Rather than outputting DOM, we'll just log.
console.log("data:", data);
console.log("callback:", callback());
return {
increase: () => setData(data + 1)
}
}
let instance = MyComponent(); // Let's 'render' our component...
instance.increase(); // This would trigger a re-render, so we call our component again...
instance = MyComponent();
instance.increase(); // and again...
instance = MyComponent();
I came here with a similar, rather vague uncertainty about the way useCallback works, its interaction with closures, and the way they are "frozen" by it. I'd like to expand a bit on the accepted answer by proposing to look at the following setup, which shows the working of useCallback (the important aspect is to ignore the linter's warning, for pedagogical reasons):
function App() {
const [a, setA] = useState(0)
const incrementWithUseCallback = useCallback(() => {
// As it closes on the first time `App` is called, the closure is "frozen" in an environment where a=0, forever
console.log(a)
setA(a + 1)
}, []) // but.. the linter should complain about this, saying that `a` should be included!
const incrementWithoutUseCallback = () => {
// This will see every value of a, as a new closure is created at every render (i.e. every time `App` is called)
console.log(a)
setA(a + 1)
}
return (
<div>
<button onClick={incrementWithUseCallback}>Increment with useCallback</button>
<button onClick={incrementWithoutUseCallback}>Increment without useCallback</button>
</div>
)
}
So we clearly see that useCallback effectively "freezes" its closure at a certain moment in time, which is a concept that must be understood clearly, in order to avoid confusing problems, which are sometimes also referred as "stale closures". This article probably does a better job of explaining it than me: https://tkdodo.eu/blog/hooks-dependencies-and-stale-closures
Here's a slightly another view on example code provided by Joe Clay, which emphasizes closure context in which callback is called.
//internal store for states and callbacks
let Store = { data: "+", callback: null };
function functionalComponent(uniqClosureName) {
const data = Store.data;//save value from store to closure variable
const callback = Store.callback = Store.callback || (() => {
console.log('Callback executed in ' + uniqClosureName + ' context');
return data;
});
console.log("data:", data, "callback():", callback());
return {
increase: () => Store.data = Store.data + "+"
}
}
let instance = functionalComponent('First render');
instance.increase();
instance = functionalComponent('Second render');
instance.increase();
instance = functionalComponent('Third render');
As you see, callback without dependencies will be always executed in the closure where it was memorized by useCallback, thus 'freezing' closure.
It happens because when function for callback is created, it is created only once, during first 'render'. Later this function is re-used, and use value of data which was recorded from Store.data during first call.
In the next example you can see the closure 'freezing' logic "in essence".
let globalX = 1;
const f = (() => {
let localX = globalX; return () => console.log(localX); }
)();
globalX = 2;//does not affect localX, it is already saved in the closure
f();//prints 1

React, Redux: how to avoid loading data into the store twice

The Set Up
I have a React/Redux application that loads a list of cats from an API.
The data gets loaded into a component like so:
// thunk, etc omitted for clarity.
componentDidMount() {
if(!this.props.loaded){
this.props.actions.loadRooms();
}
}
Which draws its props from here:
function mapStateToProps(state, ownProps) {
return {
cats: state.cats.items,
loaded: state.cats.loaded
}
}
Assume the following:
1) cats will be needed in a different, entirely separate component, one that is not a child of the current component.
2) I have no way of knowing which of the cats requiring components will be mounted first.
The Actual Question
Is the if(!this.props.loaded) useful? Put another way, does it save me a theoretical call to the API when that other route mounts if both check for existing store data first?
If the check is useful, is there a better way to do it?
Yes, I would have your redux actions look something like: GET_CATS, GET_CATS_SUCCESS, and GET_CATS_ERROR.
GET_CATS would set the loading state in the redux store to true, that way you can interrogate it in the respective componentDidMount() functions and only make the call to the api when loading is false. I think this is a fairly common way of doing it.
It all depends on how you handle your async data fetching in redux ,if both siblings components are listening to the portion of the state that represents cats you can do:
// Component A and Component B might have something like this
// they both subscribe to the same portion of the state so, if
// data is already available then you don't need to do fetch it again.
...
componentDidMount() {
if (this.props.cats.length === 0) {
this.props.actions.loadRooms();
}
}
...
If you are using redux-thunk then you might control this at the action level:
function loadRooms() {
return (dispatch, getState) => {
if (getState().cats.length === 0) {
dispatch(loadRoomsPending());
fetchMyData(...args)
.then((res) => dispatch(loadRoomsSuccess(res))
.catch((err) => dispatch(loadRoomsError(err));
}
}
}
// Component A and Component B
...
componentDidMount() {
this.props.actions.loadRooms();
}
...
Again here you have access to the current state with getState() so it's pretty common to check if the data is already available. Now this approach comes with some boilerplate and it might get tedious in the long run (it requires for you to write another three functions loadRoomsPending, loadRoomsSuccess, loadRoomsError). This way your components don't have to manually check for it. Or if you like it more explicit or cleaner you can give a middleware I implemented a try, I was kind of frustrated by all this boilerplate so using redux-slim-async you can do this:
function loadRooms() {
return {
types: [
actionTypes.LOAD_ROOMS_PENDING,
actionTypes.LOAD_ROOMS_SUCCESS,
actionTypes.LOAD_ROOMS_ERROR,
],
callAPI: fetch(...args).then(res => res.json()),
shouldCallAPI: (state) => state.cats.length === 0,
};
}
This handles everything for you with FSA compliant actions and it's very clear what is going on. Heck if you set it up properly you can make it even better:
function loadRooms() {
return {
typePrefix: actionTypes.LOAD_ROOMS,
callAPI: fetch(...args).then(res => res.json()),
shouldCallAPI: (state) => state.cats.length === 0,
};
}
And this will fire off the pending, success and error request with the format ${typePrefix}_PENDING, ${typePrefix}_SUCCESS, ${typePrefix}_ERROR, You can find the middleware here. But by all means just use whatever you feel best fits your use case, I felt like sharing this work because it's a frustration that brought me to build a middleware to handle it. Keep in mind that I made some assumptions on your case so if I am completely off let me know.
if I understand your question correctly, you want to be able to see if a separate class is loaded its data yet. If yes, then don't call the API to load the cats again.
There are two ways to do this, let's assumed COM1 and COM2 are your components.
return the entire state instead of just the specific variables you want for both of your components:
return state
then reference the cats in each component:
this.props.COM1.cats.items
this.props.COM2.cats.items
return the specific cats variable from the other components. you do the following for each components:
function mapStateToProps(state, ownProps) {
let cats = state.COM1.cats.items;
let loaded: state.cats.loaded;
let otherCats = state.COM2.cats.items;
return {
cats,
otherCats,
loaded
}
}

Listening to store change in redux saga

I'm trying to create a redux saga that will listen to a change for one variable in the state. When it does change, I want to dispatch some other action. Is this possible?
This is what I want to do:
yield takeLatest(fooAction, fetchAll);
function* fetchAll() {
const part = yield select(getPartOfState);
if (part.flag) {
yield call(listenToChange);
}
}
function* listenToChange() {
const anotherPart = yield select(getAnotherPartOfState);
if (anotherPart === true) { // this is what I want to wait for
// do something
}
}
So I basically want to wait for anotherPart to change, because initially it will be false, and execute this in the loop just once (even if the listenToChange gets executed multiple times. Is this possible?
I adopted the pattern below, which does exactly what you describe.
It works by waiting on every action passing through the store, and repeats a selector to see if a specific value has changed, triggering a saga.
The signature is a wrapping function, which enables you to pass a selector and a saga. The saga has to accept previous and next values. The wrapping function 'hands over' to your saga once for every change in the selected value. You should author logic in your saga to 'take over' from the wrapping generator using the normal yield calls, when relevant conditions are met.
import { take, spawn, select } from "redux-saga/effects"
function* selectorChangeSaga(selector, saga) {
let previous = yield select(selector)
while (true) {
const action = yield take()
const next = yield select(selector)
if (next !== previous) {
yield* saga(next, previous)
previous = next
}
}
}
Below is a tested example which defines a saga in my application. It generates a normal saga, run in the normal way.
The logic runs whenever the state's "focusId" value changes. My sagas carry out the lazy-loading of remote data corresponding with the id, and opportunistically refresh the lists from a server. Note the asterisks, especially the yield * delegating yield ! It defines how the generators 'hand off' to each other.
//load row when non-null id comes into focus
function* focusIdSaga() {
yield* selectorChangeSaga(state => state.focusId, function* (focusId, prevFocusId) {
const { focusType, rows } = yield select()
if (focusType) {
if (!prevFocusId) { //focusId previously new row (null id)
//ensure id list is refreshed to include saved row
yield spawn(loadIdsSaga, focusType)
}
if (focusId) { //newly focused row
if (!rows[focusId]) {
//ensure it's loaded
yield spawn(loadRowSaga, focusType, focusId)
}
}
}
})
}
By contrast with #alex and #vonD I am personally comfortable monitoring state, and I feel it performs adequately and offers a terse and reliable way not to miss the change you care about without unnecessary indirection. If you only track actions, it is easy to introduce bugs by creating an action which changes state, while not remembering to add the action type to your filter. However, if you consider performance of the repeated selector to be an issue, you can narrow the filter of the 'take' in order to only respond to certain actions which you KNOW to affect the part of the state tree you are monitoring.
UPDATE
Building on the approach shown by #vonD I have refactored the example above in a way which is a bit cleaner. The monitorSelector() function interacts with the conventional yield-based flow of a saga without wrapping anything. It provides a way for a saga to 'block' to wait for a changing value.
function* monitorSelector(selector, previousValue, takePattern = "*") {
while (true) {
const nextValue = yield select(selector)
if (nextValue !== previousValue) {
return nextValue
}
yield take(takePattern)
}
}
This is the tested version of the saga from the original example, but refactored for the new way of monitoring state.
//load row when non-null id comes into focus
function* focusIdSaga() {
let previousFocusId
while (true) {
const focusId = yield* monitorSelector(state => state.focusId, previousFocusId)
const { focusType, rows } = yield select()
if (focusType) {
if (!previousFocusId) { //focusId previously new row (null id)
//ensure id list is refreshed to include saved row
yield spawn(loadIdsSaga, focusType)
}
if (focusId) { //newly focused row
if (!rows[focusId]) {
//ensure it's loaded
yield spawn(loadRowSaga, focusType, focusId)
}
}
}
previousFocusId = focusId
}
}
As Alex mentioned in his comment, listening to a state change comes down to listening to the actions that are likely to trigger a change of this piece of state.
The take effect can take various patterns describing actions as a parameter, which can help you do just that: an action, an array of actions, a function, etc.. If you don't want to whitelist such actions, you can even call take without an argument (or with the string '*' if you want to be more explicit), which gives you a chance to inspect state after every action.
With this is mind, a saga waiting for a piece of state to have a given value could be written like this:
function *waitForStateToHaveValue(selector, expectedValue) {
let stateSlice = yield select(selector);
while (stateSlice !== expectedValue) {
yield take();
stateSlice = yield select(selector);
}
}

Resources