React & RTK: How to dispatch from a middleware? - reactjs

I'm using RTK (Redux Toolkit) and RTK Query to manage my store and API requests.
To catch errors globally, I followed this example. That works fine, but now I'd like to log out the user when a request was rejected because of a 401 - Unauthorized-error.
const rtkQueryErrorLogger = api => next => action => {
if (isRejectedWithValue(action)) {
if (action.payload.status === 401) {
// Reset the store (not working)
const dispatch = useDispatch();
const dipatch(logout());
// Forward to login-screen (not working)
const navigation = useNavigation();
navigation.push('Login');
}
}
return next(action);
};
Any idea? Thanks in advance!

A Redux middleware always gets a "mini-store API" object as the argument to the outermost function, which contains {dispatch, getState}:
https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
In this case, it's the variable named api in your example.
You can access dispatch from that object and dispatch an action at any time inside your middleware. Just delete the const dispatch = useDispatch line, and either change the next line to api.dispatch() or destructure dispatch from the api object in the declaration.

Related

redux thunk wait data from a dispatch before to do an another dispatch

I'm trying to improve my code by trying to use redux thunk as well as possible but I've searched a lot, no solution corresponds to my research.
Basically, with the twitch API we have to make 2 API calls, one to get the authentication key and another to get the data from this key.
Here is my current code:
dispatch(getOauthKey())
}, [])
useEffect(() => {
if(refresh){
dispatch(getAllStreams(oAuthKey.access_token))
}
}, [oAuthKey.access_token])
Here I had to use a true/false with refresh to know when to dispatch or not and [oAuthkey.access_token] to restart the useEffect when the variable receives data.
It works but it's not optimized at all and I know you can chain dispatches with thunk but I couldn't do it. Here is what I tried to do:
const thunkA = () => (dispatch, getState) => {
dispatch(getOauthKey())
}
const thunkB = (key) =>(dispatch, getState) => {
dispatch(getAllStreams(key))
}
console.log(oAuthKey.access_token)
useEffect(()=> {
dispatch(thunkA()).then(() =>dispatch(thunkB(oAuthKey.access_token))
);
}, [])
And I have this as an error:
TypeError: Cannot read properties of undefined (reading 'then'). I specify that the first call api sends the key into the store so the oAuthKey comes from the latter
Sorry I'm really new to reactjs / redux / thunk and the problem doesn't jump out at me here haha.
thank a lot in advance
There are several options to do that. as a standard redux-thunk solution, you can use promise changing for dispatching the actions in the thunk.
First of all, you need to add this package: redux-promise-middleware with the official documentation.
Given a single action with an async payload, the middleware transforms the action to separate pending action and a separate fulfilled/rejected action, representing the states of the async action.
Now you can chain your actions:
const chainMyActions = () => {
return (dispatch) => {
const response = dispatch(thunkA());
response.then((data) => {
dispatch(thunkB(data.access_token))
})
}
}
Now you can call the sample chainMyActions function in your useEffect.

How to stop the multiple API request in react js functional component

I am new in react js. I have a function and one GET API request. I need to dispatch the API action in the functional component. When I call the dispatch function in my functional component. It's re-render multiple times (Multiple requests coming). When I use the dispatch inside the useEffect My API is not dispatched.
Without useEffect dispatch function called:(API called multiple times)
export default LogoCard {
let id=1;
const dispatch = useDispatch();
dispatch(viewLogDetails(id));
}
using useEffect dispatch function called.(dispatch function not called using useEffect)
export default LogoCard {
let id=1;
const dispatch = useDispatch();
useEffect(()=>{
dispatch(viewLogDetails(id));
},[dispatch]);
}
Redux Action:
export const viewTimeLogging = createAsyncThunk(
"logoReducer/logo/Viewlogo",
async (id, { getState }) => {
const response = await axios.get(`/viewLogDetails?id=${id}`);
let data = null;
data = await response.data;
return data.viewLogo;
}
);
I've noticed that the method you are calling inside your dispatch has a different name.
If you need the axios method to be called only when id changes you should use the useEffect hook with [dispatch, id] dependencies (I assume that you will pass the id as a props or gather it in some other way in the future instead of having hardcoded like that).
If you want to call the dispatch on every re-render of the component you can either put it inside the function like the first piece of code you wrote (but I would not do that).
https://codesandbox.io/s/mystifying-raman-tul4j?file=/src/App.js
Here's a sandbox with the code you might want

React / Redux custom middleware - useDispatch not updating the state

I would like to create some middleware to check a tokens expires_at field refreshing the token if necessary and updating the state for the token fields.
I have used the redux toolkit to create the redux functionality, making use of slices. I have a slice that will update the state with token data and this works on the callback for the initial token.
When creating the middleware I can not get the slice to be called and therefore the state remains unchanged.
As a simple spike without an api call:
import { useSelector, useDispatch } from 'react-redux';
import { setTokens } from '../features/tokens/tokenSlice';
const refresher = () => ({ dispatch, getState }) => (next) => (action) => {
const exp_at = useSelector((state) => state.tokens.expires_at);
if(hasBreachedThreshold(exp_at)){
dispatch = useDispatch();
dispatch(
setTokens({
access_token: 'new token',
expires_at: 637423776000000000, -- hard coded date way in the future.
})
}
... else carry on.
);
I would expect when the middleware is called, on the first pass hasBreachedThreshold() returns true and the dispatch method would call the slice reducer and update the state. Any further runs would pass over as hasBreachedThreshold() would return false - for a while anyway.
What is happening though is that the hasBreachThreshold always returns false because the state is never updated, causing an indefinite loop.
The middleware is configured correctly and is called.
The expires_at value is extracted from the state.
hasBreachThreshold() is tested thoroughly and behaves correctly.
Being fairly new to React / Redux I expect my understanding of how and when to use dispatch is wrong. I assumed I could use dispatch similarly to how I do in my components, is this not the case? or am I going about this totally the wrong way?
When writing a middleware you have already the dispatch function and Redux store available through the function params:
// Use `dispatch` & `getState`
const refresher = () => ({ dispatch, getState }) => (next) => (action) => {
const exp_at = getState().tokens.expires_at;
if(hasBreachedThreshold(exp_at)){
dispatch(
setTokens({
access_token: 'new token',
expires_at: 637423776000000000, -- hard coded date way in the future.
})
}
);
Also you have essential mistake of when to use React hooks, refer to Rules of Hooks.
Hooks are not available in context of Redux middleware.

How redux-thunk works?

So I am currently new with redux and stuck in the middleware part. I need to know how these two codes interact with each other.
My action creator:
import jsonPlaceHolder from '../APis/jsonPlaceHolder';
export const fetchPosts= ()=>{
return async (dispatch)=>{
const response = await jsonPlaceHolder.get('/posts');
dispatch({type:'FETCH_POSTS',payload: response});
};
};
redux-thunk code:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
Plain redux only works with sync actions. Redux thunk gives you the ability to work with async actions (to dispatch multiple actions from a single action creator, for async actions that is usually the REQUEST/RESPONSE/ERROR action). Middleware is something that stands between you dispatching the action and reducer updating the store. Since redux only works with plain objects, to use a action creator (like fetchPosts) you need something (redux-thunk here). It simply injects the dispatch parameter (and getState that gives you the ability to get the current state if your action creator depends on it).
The next(action) inside middleware is the method that propagates your action object to the next middleware(or if it is the last one to your reducer). Redux-thunk checks if the thing you dispatched is a function(since we said that redux can only work with plain objects), and if it is a function it just injects the above mentioned parameters.
So it is basically:
dispatch(fetchPosts()) -> redux-thunk-middleware -> it is function so
lt's call it with injected dispatch/getState (this will not be propagated to the reducer) ->
dispatch({type:'FETCH_POSTS',payload: response}) ->
redux-thunk-middleware -> not a function, let it through -> reducer ->
update state
Hope this helps.

React onClick delete dispatch won't send second dispatch request after response received

In a component I have a button that onClick dispatches a deleteQuestion action that sends a fetch backend delete request, and when the response is received is supposed to call another action to update the Redux store.
However, since it's an onClick event, the deleteQuestion thunk function does not work like a traditional dispatch request made from ComponentWillMount and instead returns an anonymous function with a dispatch parameter that never is called. Therefore, I'm required to call the dispatch twice simultaneously in the onClick method like so:
handleDelete = () => {
const { questionId } = this.props.match.params
const { history } = this.props
deleteQuestion(questionId, history)(deleteQuestion); //calling method twice
}
While this approach is effective for trigging the delete request to the Rails backend, when I receive the response, the second dispatch function that I have embedded in the deleteQuestion action -- dispatch(removeQuestion(questionId)) -- won't trigger to update the Redux store. I've tried placing multiple debuggers in the store and checking console and terminal for errors, but nothing occurs.
I've read through the Redux docs and other resources online and from what I've been able to find they all say it should be possible to include a second dispatch call in a .then request. While it's possible to do this in get, post, and patch requests, I can't figure out why it won't work in a delete request.
The thunk call I make is:
export function deleteQuestion(questionId, routerHistory) {
return (dispatch) => {
fetch(`${API_URL}/questions/${questionId}`, {
method: 'DELETE',
}).then(res => {
dispatch(removeQuestion(questionId))
})
}
}
And the github is:
https://github.com/jwolfe890/react_project1/blob/master/stumped-app-client/src/actions/questions.js
I'd really appreciate any insight, as I've been trying to get passed this for two days now!
You are calling the action deleteQuestion directly instead of having your store dispatch the delete question action for you. You should instead call the deleteQuestion from your props that is already mapped to dispatch:
handleDelete = () => {
const { questionId } = this.props.match.params
const { history } = this.props
this.props.deleteQuestion(questionId, history);
}
If you pass in an object as mapDispatchToProps each element is dispatch call. In other words your mapDispatchToProps is equivalent to:
(dispatch) => ({
deleteQuestion: (...params) => dispatch(deleteQuestion(...params))
})

Resources