React Redux - how to trigger action after another action without Component - reactjs

I'm using react-persist to save redux state to local storage.
I want to make an api call based on the persisted state, so I want to dispatch the request call right after the action persist/REHYDRATE (defined by the library and executed on its own) occurs.
Which is the best way to achieve this?

The posted answer has a mistake so I thought of improving the mistaken answer:
According to the question "I want to dispatch the request call right AFTER the action persist/REHYDRATE".
import {API_CALL} from "./ACTION/API/index"
let hasRehydrated = false;
const onRehydrationMiddleware = store => next => action => {
// if this Action happens to be 'persist/REHYDRATE' then follow it up with your
// desired Action
if (!hasRehydrated && action.type === 'persist/REHYDRATE') {
hasRehydrated = true;
next(action); // this is to make sure you get the Rehydrated State before making
// the store.dispatch(API_CALL()) call
store.dispatch(API_CALL()); //
} else {
// make a call to the next action
next(action);
}
};
thanks to timotgl for his answer

Try this middleware:
let hasRehydrated = false;
const onRehydrationMiddleware = store => next => action => {
if (!hasRehydrated && action.type === 'persist/REHYDRATE') {
hasRehydrated = true;
store.dispatch(callApi());
} else {
next(action);
}
};
You might not need to remember wether persist/REHYDRATE was already dispatched before (I wasn't sure if it happens only once), in this case just remove the flag. Otherwise it will remember the event in module scope where your middleware lives, which works fine as a poor man's singleton pattern.
The middleware is basically never needed again after this event, but I don't think it's possible to throw them out after creating the store. You could use store.subscribe(listener) but the listener doesn't see actions. Another option would be to detect that rehydration happened based on the state, which I'm assuming changes in some way. Then store.subscribe would work, and you could call the api and unsubscribe right after.

Related

RTK Query hooks - preventing polling to auto refetch when arg change

I'm trying to refresh a token in a Query hook, with the polling feature every 9 seconds:
"/App.tsx"
..
...
const [storedToken, setStoredToken] = useState(getStoredToken());
const { data, error, refetch } = useRefreshUserQuery(storedToken, {
pollingInterval: 9000,
// refetchOnMountOrArgChange: false // -> This has no effect
});
...
..
The problem is, it re-fetches instantly when the token is set with setStoredToken(token). The new token is passed as argument to the Query hook storedToken and refetch immediately (like an infinite loop).
That would be pretty neat to be able to do this. Is there any better way to refresh a token with polling?
I believe that issue is nothing to solve on RTK-Q level - it's a pretty common and expected "limitation" of hooks and rendering lifecycle architecture. And I feel that RTK-Q polling just won't fit your requirements here, of course, that you are trying to achieve - it's not actually polling in common sense. At least - it's conditional polling, which needs some more logic)
So I would solve this just by debouncing and useEffect:
const [storedToken, setStoredToken] = useState<string>(getStoredToken());
const [tokenDebounced] = useDebounce(storedToken, 9000);
const { data } = useRefreshUserQuery(tokenDebounced);
useEffect(() => {
if (data) {
setStoredToken(data);
// console.log(newToken);
}
}, [data]);
The useEffect content and data content may differ, but the overall idea should be clear.
useDebounce is from https://www.npmjs.com/package/use-debounce,
but your own implementations should work the same if you have some defined already.
Another idea, touching you AUTH setup a bit - is just avoid
const [storedToken, setStoredToken] = useState<string>(getStoredToken());
the part at all, and keep useRefreshUserQuery() without params.
Most likely and common is to store the token in localStorage or redux\other store, and define new baseQuery, based on fetchBaseQuery that will set header and\or to include cookies with credentials: "include" with a token from localStorage or redux\other store. Definitely, you will need to store it during the first AUTH then.
I think RTK-Q auth example reveals this case in some way also:
https://redux-toolkit.js.org/rtk-query/usage/examples#authentication
After you'll avoid that useState and query hook param - you'll be able to use polling with no issues:
const { data, error, refetch } = useRefreshUserQuery(undefined ,{
pollingInterval: 9000,
});
"Polling" here means "fetch X seconds after I have data", but of course you have to get the first data itself - and that is that first fetch. If you prevent that, polling will also never start.
Tbh., this is kind of a weird requirement and doing it like this will fill your cache with dozens of state entries.
I'd do something a little differently - solve it in the endpoint lifecycle.
This is untested pseudocode and you'll need to adjust it a bit:
function waitFor(ms) {
return new Promise(resolve => setTimeout(() => resolve("waited"), ms))
}
currentToken: build.query({
query() {
// whatever you need for the first token here
},
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved }
) {
try {
// wait for the initial query to resolve before proceeding
await cacheDataLoaded
while (true) {
const result = await Promise.race(waitFor(9000), cacheEntryRemoved)
if (result !== "waited") {
// cache entry was removed, stop the loop
break
}
// make a fetch call to get a new token here
const newToken = fetch(...)
updateCachedData((oldData) => newToken)
}
},
})
and then just
const result = useCurrentTokenQuery()
in your component

How to queue requests using react/redux?

I have to pretty weird case to handle.
We have to few boxes, We can call some action on every box. When We click the button inside the box, we call some endpoint on the server (using axios). Response from the server return new updated information (about all boxes, not the only one on which we call the action).
Issue:
If user click submit button on many boxes really fast, the request call the endpoints one by one. It's sometimes causes errors, because it's calculated on the server in the wrong order (status of group of boxes depends of single box status). I know it's maybe more backend issue, but I have to try fix this on frontend.
Proposal fix:
In my opinion in this case the easiest fix is disable every submit button if any request in progress. This solution unfortunately is very slow, head of the project rejected this proposition.
What we want to goal:
In some way We want to queue the requests without disable every button. Perfect solution for me at this moment:
click first button - call endpoint, request pending on the server.
click second button - button show spinner/loading information without calling endpoint.
server get us response for the first click, only then we really call the second request.
I think something like this is huge antipattern, but I don't set the rules. ;)
I was reading about e.g. redux-observable, but if I don't have to I don't want to use other middleware for redux (now We use redux-thunk). Redux-saga it will be ok, but unfortunately I don't know this tool. I prepare simple codesandbox example (I added timeouts in redux actions for easier testing).
I have only one stupid proposal solution. Creating a array of data needs to send correct request, and inside useEffect checking if the array length is equal to 1. Something like this:
const App = ({ boxActions, inProgress, ended }) => {
const [queue, setQueue] = useState([]);
const handleSubmit = async () => { // this code do not work correctly, only show my what I was thinking about
if (queue.length === 1) {
const [data] = queue;
await boxActions.submit(data.id, data.timeout);
setQueue(queue.filter((item) => item.id !== data.id));
};
useEffect(() => {
handleSubmit();
}, [queue])
return (
<>
<div>
{config.map((item) => (
<Box
key={item.id}
id={item.id}
timeout={item.timeout}
handleSubmit={(id, timeout) => setQueue([...queue, {id, timeout}])}
inProgress={inProgress.includes(item.id)}
ended={ended.includes(item.id)}
/>
))}
</div>
</>
);
};
Any ideas?
I agree with your assessment that we ultimately need to make changes on the backend. Any user can mess with the frontend and submit requests in any order they want regardless how you organize it.
I get it though, you're looking to design the happy path on the frontend such that it works with the backend as it is currently.
It's hard to tell without knowing the use-case exactly, but there may generally be some improvements we can make from a UX perspective that will apply whether we make fixes on the backend or not.
Is there an endpoint to send multiple updates to? If so, we could debounce our network call to submit only when there is a delay in user activity.
Does the user need to be aware of order of selection and the impacts thereof? If so, it sounds like we'll need to update frontend to convey this information, which may then expose a natural solution to the situation.
It's fairly simple to create a request queue and execute them serially, but it seems potentially fraught with new challenges.
E.g. If a user clicks 5 checkboxes, and order matters, a failed execution of the second update would mean we would need to stop any further execution of boxes 3 through 5 until update 2 could be completed. We'll also need to figure out how we'll handle timeouts, retries, and backoff. There is some complexity as to how we want to convey all this to the end user.
Let's say we're completely set on going that route, however. In that case, your use of Redux for state management isn't terribly important, nor is the library you use for sending your requests.
As you suggested, we'll just create an in-memory queue of updates to be made and dequeue serially. Each time a user makes an update to a box, we'll push to that queue and attempt to send updates. Our processEvents function will retain state as to whether a request is in motion or not, which it will use to decide whether to take action or not.
Each time a user clicks a box, the event is added to the queue, and we attempt processing. If processing is already ongoing or we have no events to process, we don't take any action. Each time a processing round finishes, we check for further events to process. You'll likely want to hook into this cycle with Redux and fire new actions to indicate event success and update the state and UI for each event processed and so on. It's possible one of the libraries you use offer some feature like this as well.
// Get a better Queue implementation if queue size may get high.
class Queue {
_store = [];
enqueue = (task) => this._store.push(task);
dequeue = () => this._store.shift();
length = () => this._store.length;
}
export const createSerialProcessor = (asyncProcessingCallback) => {
const updateQueue = new Queue();
const addEvent = (params, callback) => {
updateQueue.enqueue([params, callback]);
};
const processEvents = (() => {
let isReady = true;
return async () => {
if (isReady && updateQueue.length() > 0) {
const [params, callback] = updateQueue.dequeue();
isReady = false;
await asyncProcessingCallback(params, callback); // retries and all that include
isReady = true;
processEvents();
}
};
})();
return {
process: (params, callback) => {
addEvent(params, callback);
processEvents();
}
};
};
Hope this helps.
Edit: I just noticed you included a codesandbox, which is very helpful. I've created a copy of your sandbox with updates made to achieve your end and integrate it with your Redux setup. There are some obvious shortcuts still being taken, like the Queue class, but it should be about what you're looking for: https://codesandbox.io/s/dank-feather-hqtf7?file=/src/lib/createSerialProcessor.js
In case you would like to use redux-saga, you can use the actionChannel effect in combination with the blocking call effect to achieve your goal:
Working fork:
https://codesandbox.io/s/hoh8n
Here is the code for boxSagas.js:
import {actionChannel, call, delay, put, take} from 'redux-saga/effects';
// import axios from 'axios';
import {submitSuccess, submitFailure} from '../actions/boxActions';
import {SUBMIT_REQUEST} from '../types/boxTypes';
function* requestSaga(action) {
try {
// const result = yield axios.get(`https://jsonplaceholder.typicode.com/todos`);
yield delay(action.payload.timeout);
yield put(submitSuccess(action.payload.id));
} catch (error) {
yield put(submitFailure());
}
}
export default function* boxSaga() {
const requestChannel = yield actionChannel(SUBMIT_REQUEST); // buffers incoming requests
while (true) {
const action = yield take(requestChannel); // takes a request from queue or waits for one to be added
yield call(requestSaga, action); // starts request saga and _waits_ until it is done
}
}
I am using the fact that the box reducer handles the SUBMIT_REQUEST actions immediately (and sets given id as pending), while the actionChannel+call handle them sequentially and so the actions trigger only one http request at a time.
More on action channels here:
https://redux-saga.js.org/docs/advanced/Channels/#using-the-actionchannel-effect
Just store the promise from a previous request and wait for it to resolve before initiating the next request. The example below uses a global variable for simplicity - but you can use smth else to preserve state across requests (e.g. extraArgument from thunk middleware).
// boxActions.ts
let submitCall = Promise.resolve();
export const submit = (id, timeout) => async (dispatch) => {
dispatch(submitRequest(id));
submitCall = submitCall.then(() => axios.get(`https://jsonplaceholder.typicode.com/todos`))
try {
await submitCall;
setTimeout(() => {
return dispatch(submitSuccess(id));
}, timeout);
} catch (error) {
return dispatch(submitFailure());
}
};

Using redux-observable to perform XHR then dispatch success/failure

Moving a large codebase built on redux-thunk over to redux-observable and am struggling to figure out the semantics of an epic that will:
Listen for the LOAD action
Perform an XHR
Dispatch an appropriate SUCCESS or FAILURE action
Here's what I've got so far:
export function editEpic(action$, unusedStore, { xhrClient }) {
return action$
.ofType(LOAD_ACTION)
.map(({ apiParams, operationId }) => {
// xhrClient is an object with methods on it that
// return a Promise of the data
const editOperation = xhrClient[operationId];
// call the api now
return editOperation(apiParams);
})
.map(result => {
return { type: SUCCESS_ACTION, result };
})
.catch(error => {
// We never get here..
return { type: FAILURE_ACTION, error };
});
}
This works just fine when the API call is successful, however in the event that the Promise rejects with an error, the catch operation never gets called.
Could someone point me in the right direction here? We are using:
redux-observable#0.18.0
rxjs#5.5.11
I believe the issue is that that first .map should instead be a .switchMap, .concatMap, or .mergeMap instead. To verify this, stick a log statement in the success case to see what result equals, and i expect you'll see that it's a promise, not the value that the promise resolves to. Furthermore, the promise will be in a pending state; not yet resolved/rejected.
The choice of switch vs concat vs merge will depend on what you want to do if multiple LOAD_ACTION are kicked off in quick succession before the first one can complete. Switch will cancel the first in favor of the new one; concat will wait for the first to complete before moving on to the second; merge will do them in whatever order it can, with no guarantee of the order of the results.

How to execute function when action is fired or completes?

THE SHORT QUESTION
What is best practice for executing a function after an action has been triggered?
I'm tempted to detect the action in middleware. If its the action I want, execute the function and then pass the action to the reducers. But this seems a bit hacky?
FYI The function uses jQuery to get all codemirror elements on the page and clears the history of all of them
THE FULL QUESTION WITH BACKGROUND (for those who want more depth)
The code I am working with (not my code) fires an action (FAKE_ACTION)
The payload of FAKE_ACTION is another action (REAL_ACTION).
FAKE_ACTION stores REAL_ACTION in a list in global redux state.
Later on, REAL_ACTION will be pulled from the list and triggered.
After REAL_ACTION is triggered I want to execute a function. Where should this function live?
It looks like you are wondering how to handle async operations (side effects) in a redux context. This happens indeed via a middleware but you don't have to write your own. There are projects like redux-thunk or better redux-saga. It is basically logic that stands between the dispatch and the reducers.
you have to perform asynchronus operation for this.
you can use either middleware like redux-thunk or either you can go for javascript promise or any other callback asynchronus function.
You can define a function. In your actions, post an Ajax call when you dispatch a response you will be calling either a success callback or an error callback. So in dispatch, you can define your function.
export function loginUser(email, password) {
return (dispatch, getState) => {
dispatch(loginRequest())
return ajax.post(URL_PREFIX+"/auth/login", { email, password })
.then(res => {
// console.log("path: ", path)
dispatch(loginSuccess(res))
const value = loginUtils.getCurrentUser() //function that you are talking about
})
.catch(errors => {
dispatch(loginFail(errors));
})
}
}
and in loginUtils the function getCurrentUser() is like below
export function getCurrentUser() {
return 'test'
}
Some useful answers here but none are quite what I'm after (given the messy codebase I'm working with) / didn't feel right.
I ended up using store.subscribe which allows you to subscribe to changes in the store's data and react accordingly.
See https://redux.js.org/docs/api/Store.html#subscribe for more info

Cancelling previous async action using redux-thunk

I am building a React/Redux app using the redux-thunk middleware to create and handle Ajax requests. I have a particular thunk that is fired pretty often, and I would like to cancel any previously started Ajax requests before firing a new one. Is this possible?
One approach would be to mark those requests as canceled by giving them random id and checking its status before handling the result.
The way to do this is to assign random id for this call in your first dispatch (inside the thunk) and check it in the reducer before handling the result.
const actionId = Math.random();
dispatch({type: AJAX_LOAD_CONST, id:actionId })
When you want to cancel all of the request use
dispatch({type:HANDLE_AJAX_RESPONSE, id:actionId, results: json })
When you want to handle the results don't forget to send the id that you u
and in the reducer have something like this:
function reducer(state = initialState, action) {
switch (action.type) {
case actions.AJAX_LOAD_CONST:
return Object.assign({}, state, { ajax: state.ajax.concat(action.id) });
case actions.CANCEL_ALL_AJAX:
return Object.assign({}, state, { ajax: [] });
case actions.HANDLE_AJAX_RESPONSE:
if (state.ajax.includes(action.id) {
//return state reduced with action.results here
}
return state;
}
}
If you use XMLHttpRequest or one of it's wrappers (JQuery?) you can also store the requests themselves and call request.abort(). if you use the new fetch api you do not have this luxury as promises lack this behavior.
I was recently faced with the same problem, in which I had to cancel pending or stale async redux actions. I solved it by creating a cancel redux actions middleware.
In redux we have the concepts of middlewares. So when you are sending the request it will go through a list of middlewares. Once the api responds back its response will pass through a number of middlewares before it reaches redux store and eventually your UI.
Now suppose we have written a cancel middleware. The api request will go through this middleware when it being initiated and the api response will also go through this middleware when the api responds back.
Each api request in redux is associated with an action, each action has a type and payload.
Write a middleware, which whenever an api request is done stores the action type associated. Along with it, it stores a flag which states whether to discard this action. If an action of similar type is fired again, make this flag true which says discard this action. When the api response comes for the previous action since the discard flag for this api request has been set to true, send null as response from the middleware.
Look at this blog for detailed information about this approach.
https://tech.treebo.com/redux-middlewares-an-approach-to-cancel-redux-actions-7e08b51b83ce
If you're using jquery ajax, you can make your action creator return the promise, it will be returned by the dispatch function, then it'll be possible to abort it. Here is an example :
Action creator
function doSomething() {
return (dispatch) => {
return $.ajax(...).done(...).fail(...);
}
}
Your component
componentDidMount(){
this.previousPromise = this.props.dispatch(doSomething());
}
somefnct() {
this.previousPromise.abort();
}

Resources