How to execute function when action is fired or completes? - reactjs

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

Related

How to clear & invalidate cache data using RTK Query?

I was facing a problem for sometime, that was I'm unable to clear cache using RTK query.
I tried in various ways but cache data is not clear.
I used invalidatesTag in my mutation query and it called the api instantly. But in this case I want to refetch multiple api again, but not from any rtk query or mutation. I want to make the api call after some user activity like click.
How can I solve this problem?
I made a separate function where I return api.util.invalidateTags(tag) or api.util.resetApiState().
this is my code-snipet:-
` const api = createApi({.....})
export const resetRtkCache = (tag?: String[]) => {
const api =
if (tag) {
return api.util.invalidateTags(tag)
} else {
return api.util.resetApiState()
}
}`
& I called it using dispatch method from other files
`const reloadData = () => {
dispatch(resetRtkCache())
}`
but here cache data is not removed.I think dispatch funtion is not working. I don't see the api call is being sent to server in the browser network.
But in this case I want to refetch multiple api again, but not from
any rtk query or mutation. I want to make the api call after some user
activity like click. How can I solve this problem?
So if I understood correctly what you want to achieve is to fetch some api that you have in RTK only after some kind of user interaction?
Can't you just define something like this?
const { data } = useGetYourQuery({ skip: skipUntilUserInteraction })
Where skipUntilUserInteraction is a component state variable that you will set to true and update to false based on the user interaction you need? (e.g. a click of a button).
So essentially on component render that specific endpoint will be skipped but will be fetched after the interaction that you want will happen?
wow, you actually asking so many questions at once. but I think you should definitely read the documentation because it covers all the questions you have.
so trying to answer your questions one by one.
I used invalidatesTag in my mutation query and it called the api instantly.
invalidating with Tags is one of the ways to clear the cache.
you should first set the tagTypes for your API then use those tags in mutation queries and tell the RTK query which part of entities you want to clear.
I want to refetch multiple APIs again
you can customize the query inside of a mutation or query like this example and by calling one function query you can send multiple requests at once and if you want to fetch the API again after the cache removed you do not need to do anything because RTK query will do it for you.
I want to make the API call after some user activity like click
every mutation gives u a function that you can pass to onClick like below:
import { use[Mymutation]Mutation } from 'features/api';
const MyComponenet() {
const [myMutationFunc, { isLoading, ...}] = use[Mymutation]Mutation();
return <button type='button' onClick={myMutationFunc}>Click for call mutaion</button>
}
and remember if you set providesTags for your endpoint which you were defined in tagTypes by clicking on the button and firing up the myMutationFunc you will be clearing the cache with those tags.
and if you looking for an optimistic update for the cache you can find your answer in here.
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, patch)
})
)
try {
await queryFulfilled
} catch {
patchResult.undo()
}
}

NextJS + Redux Saga + SSR

we have a project, with a nextjs, redux saga, typescript setup.
SSR is very important for our web app, but we also have a lot of relevant widgets on every page.
This is why our page is structured in a modular way, where every widget (1-2 per page) loads the data it needs.
These widgets are relevant for SEO reasons now.
My problem is, that the API requests are not made on the serverside though. Right now it only returns the defaultState of every widget on the server and only loads them on the client.
I have searched and found a lot of instructions on how to do it, but they all rely on the fact that nextjs waits for the "getInitialProps" method until it returns the result from the server.
Since that lifecycle method is only available in the "pages" folder, that doesn't work for me.
Also if I block the "getInitialProps" component, the component is never really rendered.
Our pages are structured like below:
- pages/Home
- <HomeContainer ...> (fetches data for the main page)
- <HomeComponent >
- <Widget1Container ...> (fetches data)
- <Widget2Container ...> (fetches data)
What I want is for the serverside to wait for all the requests provided, before it returns the page to the user.
Because of the complex nature of different widgets on a page, it is not possible to create "combined" endpoint where we get the data in the "pages/Home" folder.
I know it's not ideal, but how could we make sure that the server actually makes all 3 requests (homecontainer, widget1container, widget2container) and awaits there responses, before returning?
I would like to have it like angular-universal does it. Just wait until there are not open requests or promises and then just render.
Any ideas?
Any help or ideas are deeply appreciated.
thanks
since you are redux sage, in getServerSideProps, send the start signal to saga
export const getServerSideProps = wrapper.getServerSideProps(
async (context) => {
store.dispatch(
fetchWidgetsStart("add payload")
);
store.dispatch(END);
await (store as SagaStore).sagaTask.toPromise();
const state = store.getState();
// I am making up the reducer name
const widgetListState = state.widgetsList ? state.widgetList : null;
return { props: { productListState } };
}
);
inside saga function:
export function* fetchWidgetsStart() {
yield takeLatest(
WidgetListActionTypes.WIDGET_LIST_START,
fetchWidgetssAsync
);
}
function* fetchWidgetsAsync(action: IFetchWidgetssStart) {
try {
const res: AxiosResponse<IWidget[] | []> = Promise.all([
yield axios.get(fetchWidget1),
yield axios.get(fetchWidget2),
yield axios.get(fetchWidget3)
])
yield put(fetchWidgetSuccess(res));
} catch (e: any) {
yield put(
fetchWidgetFailure(
e.response && e.response.data.detail
? e.response.data.detail
: e.message
)
);
}
}
The Promise.all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will resolve when all of the input's promises have resolved, or if the input iterable contains no promises. It rejects immediately upon any of the input promises rejecting or non-promises throwing an error, and will reject with this first rejection message / error.

Kick off separate redux-saga on login and logout

I'm learning Redux-Saga and having a bit of trouble wrapping my head round the correct flow for connectng people to a chat service (Chatkit by Pusher) when they log in and disconnecting them on logout.
So far I have an "auth" saga which waits for a LOGIN_REQUEST action, logs in to a REST api using axios then stores a username and token in the store by calling a USER_SET action.
My question is, when the login happens and the credentials are stored, should I PUT a new action called something like CHAT_CONNECT which would kick off another saga to connect to Chatkit, or should I get the chat saga to listen to the LOGIN_SUCCESS being fired and act on that? Is there even any practical difference in these two approaches.
As a bonus question, what's the best way to receive and act on new websocket messages from Chatkit using Redux Sagas? Here's the boilerplate code for connecting and receiving events from chatkit.
chatManager
.connect()
.then(currentUser => {
currentUser.subscribeToRoom({
roomId: currentUser.rooms[0].id,
hooks: {
onNewMessage: message => {
console.log(`Received new message: ${message.text}`)
}
}
});
})
.catch(error => {
console.error("error:", error);
})
Regarding your first question:
My question is, when the login happens and the credentials are stored, should I PUT a new action called something like CHAT_CONNECT which would kick off another saga to connect to Chatkit, or should I get the chat saga to listen to the LOGIN_SUCCESS being fired and act on that?
With the information provided its difficult to decide which approach is ideal because either will accomplish the same functionality. The biggest difference I see between the two proposed approaches is the direction of dependency. You have two different "modules" (features, packages, ...whatever you call your chunks of code that handle a single responsiblity), lets call them log-in and connect-chat.
If you dispatch an action CHAT_CONNECT from within the log-in saga, your log-in module will be dependent to the connect-chat module. Presumably, the connect-chat action will live in the connect-chat module.
Alternatively, if your connect-chat saga waits for LOGIN_SUCCESS, then your connect-chat module will be dependent on your log-in module. Presumably, the LOGIN_SUCCESS will live in the log-in module.
There's nothing wrong with either approach. Which is best depends on your applications needs and functionality.
If you might want to connect to chat any other time then after successfully logging in, then it might make sense to dispatch CHAT_CONNECT from within your log-in saga. Because chat is no longer dependent on log in. There are several scenarios where either approach will work better than the other, but it really depends on how the rest of your application is set up.
Regarding your bonus questions:
One approach to hooking external events in redux-saga is accomplished via eventChannels. Docs: https://redux-saga.js.org/docs/api/#eventchannelsubscribe-buffer-matcher
There's a bit of boiler plate, but I found this approach makes testing easier and truly encapsulates external functionality. Here's a quick example of how I might hook up an event channel to the code snippet you provided:
export const createOnMessageChannel = () =>
eventChannel((emit) => {
chatManager
.connect()
.then(currentUser => {
currentUser.subscribeToRoom({
roomId: currentUser.rooms[0].id,
hooks: {
onNewMessage: message => emit({ message }),
}
});
})
.catch(error => emit({ error }));
return () => {
// Code to unsubscribe, e.g. chatManager.disconnet() ?
};
});
export function* onMessage({ message, error }) {
if (error) {
yield put(handleError(error));
return;
}
yield put(handleMessage(message));
}
// this is what you pass to your root saga
export function* createOnMessageSaga() {
// using call because this makes it easier to test
const channel = yield call(createOnMessageChannel);
if (!channel) return;
yield takeEvery(channel, onMessage);
}

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

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.

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